@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.
- package/dist/core/ag-ui/entry.js +2 -0
- package/dist/core/assistant-client/durable-run-subscription.d.ts +4 -1
- package/dist/core/assistant-client/durable-run-subscription.js +13 -4
- package/dist/core/durable-projection.d.ts +3 -1
- package/dist/core/durable-projection.js +40 -7
- package/dist/core/events.d.ts +3 -0
- package/dist/core/history-tool-metadata.d.ts +23 -0
- package/dist/core/history-tool-metadata.js +13 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +4 -0
- package/dist/core/persistence/title.d.ts +24 -0
- package/dist/core/persistence/title.js +60 -9
- package/dist/core/run-activity.d.ts +15 -2
- package/dist/core/run-activity.js +6 -4
- package/dist/core/run-events/entry.d.ts +12 -1
- package/dist/core/run-events/entry.js +19 -6
- package/dist/core/tool-call-detail.d.ts +51 -0
- package/dist/core/tool-call-detail.js +67 -0
- package/dist/core/turn-usage.d.ts +50 -0
- package/dist/core/turn-usage.js +114 -0
- package/dist/core/usage-format.d.ts +10 -0
- package/dist/core/usage-format.js +15 -0
- package/dist/embed-widget/browser/lazy-loader.d.ts +34 -0
- package/dist/embed-widget/browser/lazy-loader.js +116 -0
- package/dist/embed-widget/react/dialog.hooks.d.ts +28 -0
- package/dist/embed-widget/react/dialog.hooks.js +54 -0
- package/dist/embed-widget/react/embed.d.ts +1 -0
- package/dist/embed-widget/react/embed.js +5 -3
- package/dist/embed-widget/react/header.d.ts +3 -1
- package/dist/embed-widget/react/header.hooks.d.ts +13 -0
- package/dist/embed-widget/react/header.hooks.js +32 -0
- package/dist/embed-widget/react/header.js +4 -14
- package/dist/embed-widget/vanilla/index.d.ts +63 -0
- package/dist/embed-widget/vanilla/index.js +319 -0
- package/dist/embed-widget/vanilla/state.d.ts +48 -0
- package/dist/embed-widget/vanilla/state.js +120 -0
- package/dist/embed-widget/vanilla/style.css +29 -0
- package/dist/react/chat-pane.d.ts +2 -2
- package/dist/react/chat-pane.js +1 -1
- package/dist/react/components/A2uiSurfaceCard.d.ts +5 -1
- package/dist/react/components/A2uiSurfaceCard.js +9 -9
- package/dist/react/components/AttachmentPreviewModal.hooks.d.ts +7 -0
- package/dist/react/components/AttachmentPreviewModal.hooks.js +1 -1
- package/dist/react/components/ChatFab.d.ts +4 -1
- package/dist/react/components/ChatFab.js +2 -2
- package/dist/react/components/Composer.d.ts +2 -0
- package/dist/react/components/Composer.js +5 -2
- package/dist/react/components/ConversationList.d.ts +8 -1
- package/dist/react/components/ConversationList.hooks.d.ts +18 -0
- package/dist/react/components/ConversationList.hooks.js +25 -0
- package/dist/react/components/ConversationList.js +12 -5
- package/dist/react/components/ConversationList.rules.d.ts +25 -0
- package/dist/react/components/ConversationList.rules.js +19 -0
- package/dist/react/components/Markdown.js +6 -6
- package/dist/react/components/McpUiSurfaceCard.d.ts +1 -1
- package/dist/react/components/McpUiSurfaceCard.js +8 -4
- package/dist/react/components/MessageList.d.ts +8 -13
- package/dist/react/components/MessageList.hooks.d.ts +4 -0
- package/dist/react/components/MessageList.hooks.js +6 -1
- package/dist/react/components/MessageList.js +23 -4
- package/dist/react/components/MessageRow.d.ts +23 -1
- package/dist/react/components/MessageRow.js +61 -36
- package/dist/react/components/MessageRow.usage.d.ts +48 -0
- package/dist/react/components/MessageRow.usage.js +47 -0
- package/dist/react/components/MidRunUserMessage.d.ts +17 -0
- package/dist/react/components/MidRunUserMessage.js +41 -0
- package/dist/react/components/SentAttachment.d.ts +19 -0
- package/dist/react/components/SentAttachment.hooks.d.ts +44 -0
- package/dist/react/components/SentAttachment.hooks.js +56 -0
- package/dist/react/components/SentAttachment.js +14 -0
- package/dist/react/components/ToolCallGroup.d.ts +12 -0
- package/dist/react/components/ToolCallGroup.hooks.d.ts +20 -0
- package/dist/react/components/ToolCallGroup.hooks.js +23 -0
- package/dist/react/components/ToolCallGroup.js +13 -0
- package/dist/react/components/ToolCard.js +41 -22
- package/dist/react/components/mcp-ui-surface-state.d.ts +21 -0
- package/dist/react/components/mcp-ui-surface-state.js +26 -0
- package/dist/react/ext-event-renderer-registry.d.ts +7 -0
- package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
- package/dist/react/features/chat-pane/components/ChatPane.js +20 -7
- package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +44 -0
- package/dist/react/features/chat-pane/create-mid-run-message-poster.js +69 -0
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +4 -1
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +65 -10
- package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.d.ts +25 -0
- package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.js +70 -0
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +4 -0
- package/dist/react/features/chat-pane/hooks/useChatPaneSuggestion.hooks.d.ts +9 -0
- package/dist/react/features/chat-pane/hooks/useChatPaneSuggestion.hooks.js +11 -0
- package/dist/react/features/chat-pane/hooks/useChatPaneSurface.hooks.d.ts +17 -0
- package/dist/react/features/chat-pane/hooks/useChatPaneSurface.hooks.js +15 -0
- package/dist/react/features/chat-pane/index.d.ts +2 -1
- package/dist/react/features/chat-pane/index.js +1 -0
- package/dist/react/features/chat-pane/rules.d.ts +27 -0
- package/dist/react/features/chat-pane/rules.js +32 -0
- package/dist/react/features/chat-pane/styles.d.ts +1 -1
- package/dist/react/features/chat-pane/styles.js +92 -1
- package/dist/react/features/chat-pane/types.d.ts +39 -1
- package/dist/react/hooks/composer-draft-cache.d.ts +15 -0
- package/dist/react/hooks/composer-draft-cache.js +20 -0
- package/dist/react/hooks/useAnsweredCards.d.ts +20 -0
- package/dist/react/hooks/useAnsweredCards.js +23 -0
- package/dist/react/hooks/useComposer.d.ts +14 -0
- package/dist/react/hooks/useComposer.js +78 -6
- package/dist/react/hooks/useConversation.d.ts +8 -0
- package/dist/react/hooks/useConversation.js +34 -0
- package/dist/react/hooks/useRunActivity.d.ts +5 -3
- package/dist/react/hooks/useRunActivity.js +4 -3
- package/dist/react/index.d.ts +8 -2
- package/dist/react/index.js +6 -1
- package/dist/react/message-blocks.d.ts +22 -3
- package/dist/react/message-blocks.js +25 -3
- package/dist/react/tool-call-display.d.ts +90 -0
- package/dist/react/tool-call-display.js +97 -0
- package/dist/server/durable-runs/recover.js +19 -11
- package/dist/server/run-finalizer/entry.js +2 -1
- package/dist/store/ports.d.ts +25 -2
- package/dist/store/runtime/durable-run-store.js +5 -2
- package/dist/store/runtime/entry.d.ts +1 -0
- package/dist/store/runtime/entry.js +1 -0
- package/dist/store/runtime/history-tools.d.ts +27 -0
- package/dist/store/runtime/history-tools.js +169 -0
- package/dist/store/runtime/run-ledger.js +5 -2
- package/dist/store/sql/store.js +52 -0
- package/dist/testing/index.d.ts +7 -0
- package/dist/testing/index.js +22 -0
- 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
|
-
//
|
|
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
|
-
|
|
353
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
408
|
-
setQueuedTurn(guard.redact({ text: prompt }, {}));
|
|
463
|
+
enqueueTurn(guard.redact({ text: prompt }, {}));
|
|
409
464
|
composer.setDraft('');
|
|
410
465
|
conversation.cancel();
|
|
411
|
-
}, [composer, conversation,
|
|
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.',
|