@jini-ai/chat 0.5.6 → 0.5.8

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.
Files changed (127) hide show
  1. package/dist/core/ag-ui/entry.js +2 -0
  2. package/dist/core/assistant-client/durable-run-subscription.d.ts +4 -1
  3. package/dist/core/assistant-client/durable-run-subscription.js +13 -4
  4. package/dist/core/durable-projection.d.ts +3 -1
  5. package/dist/core/durable-projection.js +40 -7
  6. package/dist/core/events.d.ts +3 -0
  7. package/dist/core/history-tool-metadata.d.ts +23 -0
  8. package/dist/core/history-tool-metadata.js +13 -0
  9. package/dist/core/index.d.ts +4 -0
  10. package/dist/core/index.js +4 -0
  11. package/dist/core/persistence/title.d.ts +24 -0
  12. package/dist/core/persistence/title.js +60 -9
  13. package/dist/core/run-activity.d.ts +15 -2
  14. package/dist/core/run-activity.js +6 -4
  15. package/dist/core/run-events/entry.d.ts +12 -1
  16. package/dist/core/run-events/entry.js +19 -6
  17. package/dist/core/tool-call-detail.d.ts +51 -0
  18. package/dist/core/tool-call-detail.js +67 -0
  19. package/dist/core/turn-usage.d.ts +50 -0
  20. package/dist/core/turn-usage.js +114 -0
  21. package/dist/core/usage-format.d.ts +10 -0
  22. package/dist/core/usage-format.js +15 -0
  23. package/dist/embed-widget/browser/lazy-loader.d.ts +34 -0
  24. package/dist/embed-widget/browser/lazy-loader.js +116 -0
  25. package/dist/embed-widget/react/dialog.hooks.d.ts +28 -0
  26. package/dist/embed-widget/react/dialog.hooks.js +54 -0
  27. package/dist/embed-widget/react/embed.d.ts +1 -0
  28. package/dist/embed-widget/react/embed.js +5 -3
  29. package/dist/embed-widget/react/header.d.ts +3 -1
  30. package/dist/embed-widget/react/header.hooks.d.ts +13 -0
  31. package/dist/embed-widget/react/header.hooks.js +32 -0
  32. package/dist/embed-widget/react/header.js +4 -14
  33. package/dist/embed-widget/vanilla/index.d.ts +63 -0
  34. package/dist/embed-widget/vanilla/index.js +319 -0
  35. package/dist/embed-widget/vanilla/state.d.ts +48 -0
  36. package/dist/embed-widget/vanilla/state.js +120 -0
  37. package/dist/embed-widget/vanilla/style.css +29 -0
  38. package/dist/react/chat-pane.d.ts +2 -2
  39. package/dist/react/chat-pane.js +1 -1
  40. package/dist/react/components/A2uiSurfaceCard.d.ts +5 -1
  41. package/dist/react/components/A2uiSurfaceCard.js +9 -9
  42. package/dist/react/components/AttachmentPreviewModal.hooks.d.ts +7 -0
  43. package/dist/react/components/AttachmentPreviewModal.hooks.js +1 -1
  44. package/dist/react/components/ChatFab.d.ts +4 -1
  45. package/dist/react/components/ChatFab.js +2 -2
  46. package/dist/react/components/Composer.d.ts +2 -0
  47. package/dist/react/components/Composer.js +5 -2
  48. package/dist/react/components/ConversationList.d.ts +8 -1
  49. package/dist/react/components/ConversationList.hooks.d.ts +18 -0
  50. package/dist/react/components/ConversationList.hooks.js +25 -0
  51. package/dist/react/components/ConversationList.js +12 -5
  52. package/dist/react/components/ConversationList.rules.d.ts +25 -0
  53. package/dist/react/components/ConversationList.rules.js +19 -0
  54. package/dist/react/components/Markdown.js +6 -6
  55. package/dist/react/components/McpUiSurfaceCard.d.ts +1 -1
  56. package/dist/react/components/McpUiSurfaceCard.js +8 -4
  57. package/dist/react/components/MessageList.d.ts +8 -13
  58. package/dist/react/components/MessageList.hooks.d.ts +4 -0
  59. package/dist/react/components/MessageList.hooks.js +6 -1
  60. package/dist/react/components/MessageList.js +23 -4
  61. package/dist/react/components/MessageRow.d.ts +23 -1
  62. package/dist/react/components/MessageRow.js +61 -36
  63. package/dist/react/components/MessageRow.usage.d.ts +48 -0
  64. package/dist/react/components/MessageRow.usage.js +47 -0
  65. package/dist/react/components/MidRunUserMessage.d.ts +17 -0
  66. package/dist/react/components/MidRunUserMessage.js +41 -0
  67. package/dist/react/components/SentAttachment.d.ts +19 -0
  68. package/dist/react/components/SentAttachment.hooks.d.ts +44 -0
  69. package/dist/react/components/SentAttachment.hooks.js +56 -0
  70. package/dist/react/components/SentAttachment.js +14 -0
  71. package/dist/react/components/ToolCallGroup.d.ts +12 -0
  72. package/dist/react/components/ToolCallGroup.hooks.d.ts +20 -0
  73. package/dist/react/components/ToolCallGroup.hooks.js +23 -0
  74. package/dist/react/components/ToolCallGroup.js +13 -0
  75. package/dist/react/components/ToolCard.js +41 -22
  76. package/dist/react/components/mcp-ui-surface-state.d.ts +21 -0
  77. package/dist/react/components/mcp-ui-surface-state.js +26 -0
  78. package/dist/react/ext-event-renderer-registry.d.ts +7 -0
  79. package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
  80. package/dist/react/features/chat-pane/components/ChatPane.js +20 -7
  81. package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +44 -0
  82. package/dist/react/features/chat-pane/create-mid-run-message-poster.js +69 -0
  83. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +4 -1
  84. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +65 -10
  85. package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.d.ts +25 -0
  86. package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.js +70 -0
  87. package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +4 -0
  88. package/dist/react/features/chat-pane/hooks/useChatPaneSuggestion.hooks.d.ts +9 -0
  89. package/dist/react/features/chat-pane/hooks/useChatPaneSuggestion.hooks.js +11 -0
  90. package/dist/react/features/chat-pane/hooks/useChatPaneSurface.hooks.d.ts +17 -0
  91. package/dist/react/features/chat-pane/hooks/useChatPaneSurface.hooks.js +15 -0
  92. package/dist/react/features/chat-pane/index.d.ts +2 -1
  93. package/dist/react/features/chat-pane/index.js +1 -0
  94. package/dist/react/features/chat-pane/rules.d.ts +27 -0
  95. package/dist/react/features/chat-pane/rules.js +32 -0
  96. package/dist/react/features/chat-pane/styles.d.ts +1 -1
  97. package/dist/react/features/chat-pane/styles.js +92 -1
  98. package/dist/react/features/chat-pane/types.d.ts +39 -1
  99. package/dist/react/hooks/composer-draft-cache.d.ts +15 -0
  100. package/dist/react/hooks/composer-draft-cache.js +20 -0
  101. package/dist/react/hooks/useAnsweredCards.d.ts +20 -0
  102. package/dist/react/hooks/useAnsweredCards.js +23 -0
  103. package/dist/react/hooks/useComposer.d.ts +14 -0
  104. package/dist/react/hooks/useComposer.js +78 -6
  105. package/dist/react/hooks/useConversation.d.ts +8 -0
  106. package/dist/react/hooks/useConversation.js +34 -0
  107. package/dist/react/hooks/useRunActivity.d.ts +5 -3
  108. package/dist/react/hooks/useRunActivity.js +4 -3
  109. package/dist/react/index.d.ts +8 -2
  110. package/dist/react/index.js +6 -1
  111. package/dist/react/message-blocks.d.ts +22 -3
  112. package/dist/react/message-blocks.js +25 -3
  113. package/dist/react/tool-call-display.d.ts +90 -0
  114. package/dist/react/tool-call-display.js +97 -0
  115. package/dist/server/durable-runs/recover.js +19 -11
  116. package/dist/server/run-finalizer/entry.js +2 -1
  117. package/dist/store/ports.d.ts +25 -2
  118. package/dist/store/runtime/durable-run-store.js +5 -2
  119. package/dist/store/runtime/entry.d.ts +1 -0
  120. package/dist/store/runtime/entry.js +1 -0
  121. package/dist/store/runtime/history-tools.d.ts +27 -0
  122. package/dist/store/runtime/history-tools.js +169 -0
  123. package/dist/store/runtime/run-ledger.js +5 -2
  124. package/dist/store/sql/store.js +52 -0
  125. package/dist/testing/index.d.ts +7 -0
  126. package/dist/testing/index.js +22 -0
  127. package/package.json +48 -18
@@ -1,6 +1,6 @@
1
1
  import { useUserTextGuard } from '../../../hooks/useUserTextGuard.js';
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
- import { describeChatPaneSendBlocker, findAwaitedTypedAnswerId, findChatPaneSendBlocker, isChatPaneQueueableBlocker, isTypedAnswerTurn, resolveChatPaneSelection, } from '../rules.js';
3
+ import { appendQueuedTurn, describeChatPaneSendBlocker, findAwaitedTypedAnswerId, findChatPaneSendBlocker, findMidRunMessageRunId, isChatPaneQueueableBlocker, isTypedAnswerTurn, resolveChatPaneSelection, } from '../rules.js';
4
4
  import { definedProps } from '../../../util/defined-props.js';
5
5
  import { cacheAttachmentPreviewSource } from '../../../hooks/attachment-preview-cache.js';
6
6
  import { readCachedAttachmentBatchId, writeCachedAttachmentBatchId, } from '../../../hooks/composer-draft-cache.js';
@@ -34,6 +34,20 @@ async function deliverTypedAnswerSafely({ deliver, text }) {
34
34
  return 'failed';
35
35
  }
36
36
  }
37
+ /**
38
+ * Hands one mid-run message to the host. A throw counts as `'failed'`, which queues the text — the
39
+ * host could not say whether the run took it, and a message must never be dropped.
40
+ *
41
+ * @complexity O(1) plus the host's own request.
42
+ */
43
+ async function deliverMidRunMessageSafely({ deliver, runId, text }) {
44
+ try {
45
+ return await deliver({ runId, text });
46
+ }
47
+ catch {
48
+ return 'failed';
49
+ }
50
+ }
37
51
  /** Module scope so each branch costs 1 cognitive point instead of 2 (nested one level inside
38
52
  * `useChatPane` where it used to live as a ternary chain). */
39
53
  function resolveChatPaneActivity(selectedAgent, conversation) {
@@ -129,6 +143,7 @@ export function useChatPane(options) {
129
143
  redactUserText: options.redactUserText,
130
144
  onSecretRedacted: guard.notifyRedaction,
131
145
  initialMessages: options.initialMessages,
146
+ refreshSignal: options.refreshSignal,
132
147
  conversationId: options.conversationId,
133
148
  // Keys off an empty string, not `undefined` — `selection.agentId` is always a string (never
134
149
  // absent), so only the falsy "no agent selected" case should omit the key.
@@ -344,20 +359,61 @@ export function useChatPane(options) {
344
359
  if (latestDraftRef.current === submittedDraft)
345
360
  composer.setDraft('');
346
361
  }, [awaitedQuestionId, composer, guard.redactText]);
347
- // The ordinary next-turn path: queue behind a running run, else send now.
362
+ // Every queue path goes through here: a message joins the turn already queued behind this run
363
+ // (`appendQueuedTurn`) instead of replacing it, which dropped the earlier message. A turn queued
364
+ // against another conversation is not joined — it would never be flushed there anyway.
365
+ const enqueueTurn = useCallback((turn) => {
366
+ const sameConversation = queuedConversationIdRef.current === options.conversationId;
367
+ queuedConversationIdRef.current = options.conversationId;
368
+ setQueuedTurn((queued) => appendQueuedTurn({ queued: sameConversation ? queued : null, next: turn }));
369
+ }, [options.conversationId]);
370
+ // A text-only message sent while a run streams goes INTO that run when the host can deliver it.
371
+ // The draft clears before the await so the human sees it go out; whatever the outcome, the text
372
+ // ends up somewhere — in the run, or queued as the next turn (after stopping the run when the
373
+ // agent cannot take input mid-run). Never cancel on 'not-running'/'failed': the run is ending.
374
+ const sendIntoRunningTurn = useCallback(async ({ deliver, runId, turn }) => {
375
+ composer.setDraft('');
376
+ const outcome = await deliverMidRunMessageSafely({ deliver, runId, text: turn.text });
377
+ if (outcome === 'delivered' || !mountedRef.current)
378
+ return;
379
+ enqueueTurn(turn);
380
+ if (outcome === 'unsupported')
381
+ conversation.cancel();
382
+ }, [composer, conversation, enqueueTurn]);
383
+ // The ordinary next-turn path: into the running run, else queue behind it, else send now.
348
384
  const sendOrdinaryTurn = useCallback(async (prompt) => {
349
- // Queue instead of no-op'ing. `setDraft('')` and NOT `composer.reset()`: reset also discards
350
- // staged attachments, which this turn still needs when it finally goes out.
351
385
  if (isChatPaneQueueableBlocker({ blocker: sendBlocker })) {
352
- queuedConversationIdRef.current = options.conversationId;
353
- setQueuedTurn(guard.redact({ text: prompt }, {}));
386
+ const turn = guard.redact({ text: prompt }, {});
387
+ const deliver = options.deliverMidRunMessage;
388
+ const runId = deliver === undefined ? null : findMidRunMessageRunId({
389
+ blocker: sendBlocker,
390
+ attachmentCount: composer.attachments.length,
391
+ messages: conversation.messages,
392
+ });
393
+ if (deliver !== undefined && runId !== null) {
394
+ await sendIntoRunningTurn({ deliver, runId, turn });
395
+ return;
396
+ }
397
+ // Queue instead of no-op'ing. `setDraft('')` and NOT `composer.reset()`: reset also discards
398
+ // staged attachments, which this turn still needs when it finally goes out.
399
+ enqueueTurn(turn);
354
400
  composer.setDraft('');
355
401
  return;
356
402
  }
357
403
  if (!canSend)
358
404
  return;
359
405
  await sendPrompt(prompt);
360
- }, [canSend, composer, options.conversationId, sendBlocker, sendPrompt, guard.redact]);
406
+ }, [
407
+ canSend,
408
+ composer,
409
+ conversation.messages,
410
+ enqueueTurn,
411
+ options.deliverMidRunMessage,
412
+ sendBlocker,
413
+ sendIntoRunningTurn,
414
+ sendPrompt,
415
+ guard.redact,
416
+ ]);
361
417
  const send = useCallback(async () => {
362
418
  const prompt = composerPrompt(composer);
363
419
  if (!prompt || deliveringTypedAnswerRef.current)
@@ -404,11 +460,10 @@ export function useChatPane(options) {
404
460
  const prompt = composerPrompt(composer);
405
461
  if (!prompt)
406
462
  return;
407
- queuedConversationIdRef.current = options.conversationId;
408
- setQueuedTurn(guard.redact({ text: prompt }, {}));
463
+ enqueueTurn(guard.redact({ text: prompt }, {}));
409
464
  composer.setDraft('');
410
465
  conversation.cancel();
411
- }, [composer, conversation, options.conversationId, guard.redact]);
466
+ }, [composer, conversation, enqueueTurn, guard.redact]);
412
467
  const cancelQueued = useCallback(() => {
413
468
  queuedConversationIdRef.current = undefined;
414
469
  setQueuedTurn(null);
@@ -0,0 +1,25 @@
1
+ import type { MouseEvent as ReactMouseEvent } from 'react';
2
+ /**
3
+ * Decides whether a click inside the pane should open a link in a new tab.
4
+ *
5
+ * @param input.event - The click, native (React's `nativeEvent` or a DOM listener's own).
6
+ * @param input.root - The pane root; an anchor outside it (one wrapping the whole pane) is ignored.
7
+ * @returns The anchor's resolved absolute URL to open, or `null` to leave the click to the browser.
8
+ * @complexity O(d) for `closest` over the target's ancestor depth d.
9
+ */
10
+ export declare function newTabLinkHref({ event, root }: {
11
+ event: MouseEvent;
12
+ root: Element;
13
+ }): string | null;
14
+ /**
15
+ * The pane root's `onClickCapture` handler: opens qualifying link clicks in a new tab and cancels
16
+ * the in-tab navigation. See this module's doc for what qualifies.
17
+ *
18
+ * @param _required - Reserved; no required inputs.
19
+ * @param options.openInNewTab - Opener port; defaults to `window.open(url, '_blank', 'noopener,noreferrer')`.
20
+ * @returns A stable click-capture handler.
21
+ */
22
+ export declare function useChatPaneNewTabLinks(_required?: Record<string, never>, { openInNewTab }?: {
23
+ openInNewTab?: (url: string) => void;
24
+ }): (event: ReactMouseEvent<HTMLElement>) => void;
25
+ //# sourceMappingURL=useChatPaneNewTabLinks.hooks.d.ts.map
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @module useChatPaneNewTabLinks
3
+ *
4
+ * Every link inside the chat pane opens in a new tab, so following one never navigates the tab the
5
+ * conversation lives in (owner 2026-10-08: "every link"). One capture-phase click guard on the
6
+ * pane's root absorbs every link source at once — markdown in messages, tool cards, host-registered
7
+ * ext-event cards, A2UI surfaces, the header slot — instead of each renderer setting `target` on its
8
+ * own anchors and the next new card forgetting to.
9
+ *
10
+ * Capture phase, then `preventDefault()`: the guard runs before any anchor's own `onClick`, so a
11
+ * router `<Link>` (React Router, TanStack Router — both skip their in-app navigation once
12
+ * `defaultPrevented` is set) does not ALSO navigate the chat's tab after the new tab opens.
13
+ *
14
+ * Left to the browser: modified or non-primary clicks (the browser already opens those however the
15
+ * user asked), anchors that already say `target="_blank"`, `#` same-document anchors, `download`
16
+ * links, `role="button"` anchors (an action, not a link), and any scheme other than http(s) —
17
+ * `mailto:`/`tel:` hand off to another app without navigating the tab anyway.
18
+ *
19
+ * MCP-UI Views render inside a sandboxed iframe, out of this guard's reach; their proxy page
20
+ * forwards link clicks as `ui/open-link` (`@jini-ai/ui/mcp-ui`'s sandbox proxy) instead.
21
+ */
22
+ import { useCallback } from 'react';
23
+ const NEW_TAB_PROTOCOLS = new Set(['http:', 'https:']);
24
+ /** Opens `url` in a new tab with no opener or referrer — `rel="noopener noreferrer"` for `window.open`. */
25
+ function openInNewTabDefault(url) {
26
+ window.open(url, '_blank', 'noopener,noreferrer');
27
+ }
28
+ /**
29
+ * Decides whether a click inside the pane should open a link in a new tab.
30
+ *
31
+ * @param input.event - The click, native (React's `nativeEvent` or a DOM listener's own).
32
+ * @param input.root - The pane root; an anchor outside it (one wrapping the whole pane) is ignored.
33
+ * @returns The anchor's resolved absolute URL to open, or `null` to leave the click to the browser.
34
+ * @complexity O(d) for `closest` over the target's ancestor depth d.
35
+ */
36
+ export function newTabLinkHref({ event, root }) {
37
+ if (event.defaultPrevented || event.button !== 0)
38
+ return null;
39
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
40
+ return null;
41
+ const target = event.target;
42
+ if (!(target instanceof Element))
43
+ return null;
44
+ const link = target.closest('a[href]');
45
+ if (!(link instanceof HTMLAnchorElement) || !root.contains(link))
46
+ return null;
47
+ if (link.target === '_blank' || link.hasAttribute('download') || link.getAttribute('role') === 'button')
48
+ return null;
49
+ if (link.getAttribute('href').startsWith('#'))
50
+ return null;
51
+ return NEW_TAB_PROTOCOLS.has(link.protocol) ? link.href : null;
52
+ }
53
+ /**
54
+ * The pane root's `onClickCapture` handler: opens qualifying link clicks in a new tab and cancels
55
+ * the in-tab navigation. See this module's doc for what qualifies.
56
+ *
57
+ * @param _required - Reserved; no required inputs.
58
+ * @param options.openInNewTab - Opener port; defaults to `window.open(url, '_blank', 'noopener,noreferrer')`.
59
+ * @returns A stable click-capture handler.
60
+ */
61
+ export function useChatPaneNewTabLinks(_required = {}, { openInNewTab = openInNewTabDefault } = {}) {
62
+ return useCallback((event) => {
63
+ const href = newTabLinkHref({ event: event.nativeEvent, root: event.currentTarget });
64
+ if (href === null)
65
+ return;
66
+ event.preventDefault();
67
+ openInNewTab(href);
68
+ }, [openInNewTab]);
69
+ }
70
+ //# sourceMappingURL=useChatPaneNewTabLinks.hooks.js.map
@@ -57,6 +57,10 @@ export function useChatPaneRuntimeInventory({ access, initialAgents = EMPTY_AGEN
57
57
  const nextAgents = await (rescan ? access.rescanAgents() : access.listAgents());
58
58
  token.ensureCurrent();
59
59
  retry.answered();
60
+ // Successful cached data paints immediately. Poll only while the host says its single
61
+ // background scan is pending; reads never imply an explicit Rescan.
62
+ if (nextAgents.some(agent => agent.discoveryPending))
63
+ retry.schedule(() => void loadAgents(false));
60
64
  setAgents([...nextAgents]);
61
65
  setConnectingAgents(false);
62
66
  setScanningAgents(false);
@@ -0,0 +1,9 @@
1
+ import { type RefObject } from 'react';
2
+ /** Fill the pane-owned draft without sending, then focus its composer for editing.
3
+ * @complexity O(pane controls) per selection; O(1) retained state.
4
+ */
5
+ export declare function useChatPaneSuggestion({ rootRef, setDraft }: {
6
+ rootRef: RefObject<HTMLElement | null>;
7
+ setDraft: (draft: string) => void;
8
+ }, _options?: {}): (suggestion: string) => void;
9
+ //# sourceMappingURL=useChatPaneSuggestion.hooks.d.ts.map
@@ -0,0 +1,11 @@
1
+ import { useCallback } from 'react';
2
+ /** Fill the pane-owned draft without sending, then focus its composer for editing.
3
+ * @complexity O(pane controls) per selection; O(1) retained state.
4
+ */
5
+ export function useChatPaneSuggestion({ rootRef, setDraft }, _options = {}) {
6
+ return useCallback((suggestion) => {
7
+ setDraft(suggestion);
8
+ rootRef.current?.querySelector('.jini-composer-input:not(:disabled)')?.focus();
9
+ }, [rootRef, setDraft]);
10
+ }
11
+ //# sourceMappingURL=useChatPaneSuggestion.hooks.js.map
@@ -0,0 +1,17 @@
1
+ import type { ChatPaneProps } from '../types.js';
2
+ /**
3
+ * Embed is a visitor surface with a fixed backend. Workspace composer extensions and filesystem
4
+ * controls stay outside its render tree, even if a host accidentally supplies them.
5
+ * @complexity O(1) time and space.
6
+ */
7
+ export declare function useChatPaneSurface({ variant, composerSlots, workingDirectoryControlPlacement, uploadAttachments }: Pick<ChatPaneProps, 'variant'> & {
8
+ composerSlots: ChatPaneProps['composerSlots'];
9
+ workingDirectoryControlPlacement: ChatPaneProps['workingDirectoryControlPlacement'];
10
+ uploadAttachments: ChatPaneProps['uploadAttachments'];
11
+ }, _options?: {}): {
12
+ embed: boolean;
13
+ composerSlots: Omit<import("../../../slots.js").ComposerSlots, "footerAccessories" | "leadingAccessories"> | undefined;
14
+ workingDirectoryControlPlacement: "none" | "below" | "composer";
15
+ uploadAttachments: ((files: File[], options?: import("../types.js").ChatPaneAttachmentUploadOptions) => Promise<import("../../../../core/messages.js").ChatAttachment[]>) | undefined;
16
+ };
17
+ //# sourceMappingURL=useChatPaneSurface.hooks.d.ts.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Embed is a visitor surface with a fixed backend. Workspace composer extensions and filesystem
3
+ * controls stay outside its render tree, even if a host accidentally supplies them.
4
+ * @complexity O(1) time and space.
5
+ */
6
+ export function useChatPaneSurface({ variant, composerSlots, workingDirectoryControlPlacement, uploadAttachments }, _options = {}) {
7
+ const embed = variant === 'embed';
8
+ return {
9
+ embed,
10
+ composerSlots: embed ? undefined : composerSlots,
11
+ workingDirectoryControlPlacement: embed ? 'none' : workingDirectoryControlPlacement ?? 'below',
12
+ uploadAttachments: embed ? undefined : uploadAttachments,
13
+ };
14
+ }
15
+ //# sourceMappingURL=useChatPaneSurface.hooks.js.map
@@ -1,8 +1,9 @@
1
- export type { AgentRuntimePickerProps, ByokRuntimeSummary, ChatPaneActivity, ChatPaneAgent, ChatPaneAgentBridgeAccess, ChatPaneAgentControlOptions, ChatPaneAgentOption, ChatPaneAgentSelection, ChatPaneAgentToolAction, ChatPaneAttachmentUploadOptions, ChatPaneComposerHandle, ChatPaneProps, ChatPaneRunContext, ChatPaneRunContextInput, ChatPaneRuntimeAccess, ChatPaneVariant, ChatPaneWorkingDirectoryAccess, DeliverTypedAnswer, RuntimePickerPlacement, TypedAnswerDelivery, TypedAnswerNotice, } from './types.js';
1
+ export type { AgentRuntimePickerProps, ByokRuntimeSummary, ChatPaneActivity, ChatPaneAgent, ChatPaneAgentBridgeAccess, ChatPaneAgentControlOptions, ChatPaneAgentOption, ChatPaneAgentSelection, ChatPaneAgentToolAction, ChatPaneAttachmentUploadOptions, ChatPaneComposerHandle, ChatPaneProps, ChatPaneRunContext, ChatPaneRunContextInput, ChatPaneRuntimeAccess, ChatPaneVariant, ChatPaneWorkingDirectoryAccess, DeliverMidRunMessage, DeliverTypedAnswer, MidRunMessageDelivery, RuntimePickerPlacement, TypedAnswerDelivery, TypedAnswerNotice, } from './types.js';
2
2
  export { CHAT_PANE_AGENT_TOOLS, type ChatPaneAgentToolDef, type ChatPaneAgentToolInputSchema, type ChatPaneAgentToolRisk, } from './agent-tools.js';
3
3
  export { defaultChatPaneSelection, orderChatPaneAgents, resolveChatPaneSelection, } from './rules.js';
4
4
  export { createDaemonAttachmentUploader, type CreateDaemonAttachmentUploaderOptions, } from './create-daemon-attachment-uploader.js';
5
5
  export { createMcpUiToolCaller, type CreateMcpUiToolCallerOptions, type McpUiToolCallRequest, } from './create-mcp-ui-tool-caller.js';
6
+ export { createMidRunMessagePoster, type CreateMidRunMessagePosterOptions, } from './create-mid-run-message-poster.js';
6
7
  export { createTypedAnswerPoster, type CreateTypedAnswerPosterOptions, } from './create-typed-answer-poster.js';
7
8
  export { useChatPane, type UseChatPaneOptions, type UseChatPaneResult, } from './hooks/useChatPane.hooks.js';
8
9
  export { useChatPaneAgentControl, type UseChatPaneAgentControlOptions, } from './hooks/useChatPaneAgentControl.hooks.js';
@@ -2,6 +2,7 @@ export { CHAT_PANE_AGENT_TOOLS, } from './agent-tools.js';
2
2
  export { defaultChatPaneSelection, orderChatPaneAgents, resolveChatPaneSelection, } from './rules.js';
3
3
  export { createDaemonAttachmentUploader, } from './create-daemon-attachment-uploader.js';
4
4
  export { createMcpUiToolCaller, } from './create-mcp-ui-tool-caller.js';
5
+ export { createMidRunMessagePoster, } from './create-mid-run-message-poster.js';
5
6
  export { createTypedAnswerPoster, } from './create-typed-answer-poster.js';
6
7
  export { useChatPane, } from './hooks/useChatPane.hooks.js';
7
8
  export { useChatPaneAgentControl, } from './hooks/useChatPaneAgentControl.hooks.js';
@@ -1,4 +1,5 @@
1
1
  import type { ChatMessage } from '@jini-ai/chat';
2
+ import type { UserTextRedaction } from '../../../core/user-text-redaction.js';
2
3
  import type { ByokRuntimeSummary, ChatPaneAgent, ChatPaneAgentSelection, TypedAnswerNotice } from './types.js';
3
4
  export declare function defaultChatPaneSelection({ agent }: {
4
5
  agent: ChatPaneAgent;
@@ -126,6 +127,32 @@ export declare function isTypedAnswerTurn({ blocker, attachmentCount, messages,
126
127
  messages: readonly ChatMessage[];
127
128
  toolName: string;
128
129
  }): boolean;
130
+ /**
131
+ * The run a composer send can go INTO instead of queueing behind, or `null`: a run is streaming
132
+ * ({@link isChatPaneQueueableBlocker}), the turn has no attachments (a mid-run message carries text
133
+ * only — the live agent's stdin takes no files), and the newest message is that run's assistant
134
+ * turn with a known `runId`.
135
+ *
136
+ * @complexity O(1).
137
+ */
138
+ export declare function findMidRunMessageRunId({ blocker, attachmentCount, messages }: {
139
+ blocker: ChatPaneSendBlocker | null;
140
+ attachmentCount: number;
141
+ messages: readonly ChatMessage[];
142
+ }): string | null;
143
+ /**
144
+ * The queued next turn after one more message joins it. The pane holds ONE queued turn, so a second
145
+ * message sent behind the same run is appended to it (a blank line apart) rather than replacing it:
146
+ * replacing silently dropped the first message (owner, 2026-10-10). One turn, not two, because both
147
+ * were written for the run still going and the agent should read them together — two queued turns
148
+ * would also start two paid runs back to back.
149
+ *
150
+ * @complexity O(n) in the combined text length.
151
+ */
152
+ export declare function appendQueuedTurn({ queued, next }: {
153
+ queued: UserTextRedaction | null;
154
+ next: UserTextRedaction;
155
+ }): UserTextRedaction;
129
156
  /** The English copy (and i18n key) for why a typed answer was not sent. */
130
157
  export declare function describeTypedAnswerNotice({ notice }: {
131
158
  notice: TypedAnswerNotice;
@@ -179,6 +179,38 @@ export function findAwaitedTypedAnswerId({ messages, toolName }) {
179
179
  export function isTypedAnswerTurn({ blocker, attachmentCount, messages, toolName }) {
180
180
  return isChatPaneQueueableBlocker({ blocker }) && attachmentCount === 0 && isAwaitingTypedAnswer({ messages, toolName });
181
181
  }
182
+ /**
183
+ * The run a composer send can go INTO instead of queueing behind, or `null`: a run is streaming
184
+ * ({@link isChatPaneQueueableBlocker}), the turn has no attachments (a mid-run message carries text
185
+ * only — the live agent's stdin takes no files), and the newest message is that run's assistant
186
+ * turn with a known `runId`.
187
+ *
188
+ * @complexity O(1).
189
+ */
190
+ export function findMidRunMessageRunId({ blocker, attachmentCount, messages }) {
191
+ if (!isChatPaneQueueableBlocker({ blocker }) || attachmentCount !== 0)
192
+ return null;
193
+ const last = messages.at(-1);
194
+ return last?.role === 'assistant' && last.runId ? last.runId : null;
195
+ }
196
+ /**
197
+ * The queued next turn after one more message joins it. The pane holds ONE queued turn, so a second
198
+ * message sent behind the same run is appended to it (a blank line apart) rather than replacing it:
199
+ * replacing silently dropped the first message (owner, 2026-10-10). One turn, not two, because both
200
+ * were written for the run still going and the agent should read them together — two queued turns
201
+ * would also start two paid runs back to back.
202
+ *
203
+ * @complexity O(n) in the combined text length.
204
+ */
205
+ export function appendQueuedTurn({ queued, next }) {
206
+ if (queued === null)
207
+ return next;
208
+ return {
209
+ text: `${queued.text}\n\n${next.text}`,
210
+ secretRedacted: queued.secretRedacted || next.secretRedacted,
211
+ count: queued.count + next.count,
212
+ };
213
+ }
182
214
  const TYPED_ANSWER_NOTICES = {
183
215
  'not-pending': 'That question is no longer waiting for an answer, so your message was not sent.',
184
216
  failed: 'Your answer could not be delivered. Try sending it again.',