@jini-ai/chat 0.6.5 → 0.6.7
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/CHANGELOG.md +12 -0
- package/dist/core/accepted-input.d.ts +54 -0
- package/dist/core/accepted-input.js +165 -0
- package/dist/core/agent-plugin-chat-state.d.ts +43 -0
- package/dist/core/agent-plugin-chat-state.js +48 -0
- package/dist/core/assistant-client/a2ui-action-poster.js +32 -28
- package/dist/core/assistant-client/messages.d.ts +1 -1
- package/dist/core/assistant-client/messages.js +2 -2
- package/dist/core/assistant-client/transport.d.ts +2 -2
- package/dist/core/assistant-client/transport.js +184 -97
- package/dist/core/assistant-content.d.ts +2 -1
- package/dist/core/assistant-content.js +9 -11
- package/dist/core/browser/page-actions.js +4 -2
- package/dist/core/composer-history.d.ts +16 -9
- package/dist/core/composer-history.js +28 -16
- package/dist/core/conversation-title.d.ts +3 -0
- package/dist/core/conversation-title.js +3 -0
- package/dist/core/durable-projection.js +20 -13
- package/dist/core/durable-runs/continuation.d.ts +4 -0
- package/dist/core/durable-runs/continuation.js +35 -6
- package/dist/core/durable-runs/ledger-contracts.d.ts +2 -1
- package/dist/core/durable-runs/policies.d.ts +1 -0
- package/dist/core/durable-runs/policies.js +1 -0
- package/dist/core/durable-runs/ports.d.ts +6 -2
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/messages.d.ts +2 -0
- package/dist/core/partial-json.d.ts +6 -7
- package/dist/core/partial-json.js +81 -47
- package/dist/core/persistence/title.js +4 -2
- package/dist/core/question-answer.d.ts +23 -0
- package/dist/core/question-answer.js +66 -0
- package/dist/core/question-form/format.d.ts +6 -6
- package/dist/core/question-form/format.js +16 -17
- package/dist/core/question-form/normalize.d.ts +18 -0
- package/dist/core/question-form/normalize.js +112 -122
- package/dist/core/question-form/scan.js +141 -203
- package/dist/core/run-activity.js +62 -49
- package/dist/core/run-events/entry.d.ts +10 -1
- package/dist/core/run-events/entry.js +117 -87
- package/dist/core/todos.d.ts +1 -1
- package/dist/core/todos.js +60 -85
- package/dist/core/tool-events.js +14 -12
- package/dist/core/transcript.js +62 -16
- package/dist/core/turn-usage.d.ts +2 -1
- package/dist/core/turn-usage.js +56 -27
- package/dist/core/user-text-redaction.js +3 -0
- package/dist/core/util/manifest.js +77 -82
- package/dist/core/util/markdown-context.js +51 -53
- package/dist/core/util/parser.js +136 -144
- package/dist/core/util/pointer.js +16 -16
- package/dist/core/util/strip.js +57 -67
- package/dist/core/util/validate.js +15 -17
- package/dist/embed-widget/browser/session-store.js +21 -12
- package/dist/embed-widget/transports/fetch-sse.js +108 -95
- package/dist/embed-widget/vanilla/index.js +81 -70
- package/dist/react/agent-bridge/frontend-session-bridge.js +80 -52
- package/dist/react/components/AttachmentPreviewModal.js +25 -1
- package/dist/react/components/ChatSettingsDialog.d.ts +13 -0
- package/dist/react/components/ChatSettingsDialog.hooks.d.ts +115 -0
- package/dist/react/components/ChatSettingsDialog.hooks.js +40 -0
- package/dist/react/components/ChatSettingsDialog.js +72 -0
- package/dist/react/components/ChatSettingsSegmented.d.ts +18 -0
- package/dist/react/components/ChatSettingsSegmented.js +11 -0
- package/dist/react/components/Composer.d.ts +5 -1
- package/dist/react/components/Composer.js +126 -82
- package/dist/react/components/ConversationList.js +25 -39
- package/dist/react/components/ConversationTitleEditor.d.ts +27 -0
- package/dist/react/components/ConversationTitleEditor.js +54 -0
- package/dist/react/components/Icon.d.ts +1 -1
- package/dist/react/components/Icon.js +25 -45
- package/dist/react/components/Markdown.js +134 -123
- package/dist/react/components/McpUiSurfaceCard.d.ts +2 -2
- package/dist/react/components/McpUiSurfaceCard.js +30 -9
- package/dist/react/components/MessageList.d.ts +13 -13
- package/dist/react/components/MessageList.hooks.d.ts +6 -1
- package/dist/react/components/MessageList.hooks.js +19 -3
- package/dist/react/components/MessageList.js +34 -10
- package/dist/react/components/MessageRow.js +48 -32
- package/dist/react/components/MessageRow.usage.d.ts +5 -2
- package/dist/react/components/MessageRow.usage.js +20 -11
- package/dist/react/components/MidRunUserMessage.js +16 -3
- package/dist/react/components/QuestionForm.js +148 -76
- package/dist/react/components/QuestionsPanel.js +1 -1
- package/dist/react/components/TodoCard.js +12 -6
- package/dist/react/components/ToolCard.js +47 -40
- package/dist/react/components/composer-discovery.js +6 -1
- package/dist/react/components/mcp-ui-surface-state.d.ts +6 -11
- package/dist/react/components/mcp-ui-surface-state.js +23 -8
- package/dist/react/ext-event-renderer-registry.d.ts +8 -0
- package/dist/react/ext-event-renderer-registry.js +5 -2
- package/dist/react/features/chat-pane/components/AgentPluginUsage.d.ts +7 -0
- package/dist/react/features/chat-pane/components/AgentPluginUsage.js +45 -0
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +55 -19
- package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
- package/dist/react/features/chat-pane/components/ChatPane.js +192 -74
- package/dist/react/features/chat-pane/components/RuntimeDropUp.d.ts +29 -0
- package/dist/react/features/chat-pane/components/RuntimeDropUp.js +45 -0
- package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +80 -47
- package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.js +44 -40
- package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +6 -6
- package/dist/react/features/chat-pane/create-mid-run-message-poster.js +8 -8
- package/dist/react/features/chat-pane/create-typed-answer-poster.d.ts +3 -3
- package/dist/react/features/chat-pane/create-typed-answer-poster.js +3 -3
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.d.ts +6 -1
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +11 -5
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +12 -7
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +340 -149
- package/dist/react/features/chat-pane/hooks/useChatPaneControlsHeight.hooks.js +7 -6
- package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.js +28 -10
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.d.ts +3 -3
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +60 -12
- package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.d.ts +49 -0
- package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.js +143 -0
- package/dist/react/features/chat-pane/model-options.d.ts +18 -1
- package/dist/react/features/chat-pane/model-options.js +49 -20
- package/dist/react/features/chat-pane/rules.d.ts +8 -9
- package/dist/react/features/chat-pane/rules.js +84 -46
- package/dist/react/features/chat-pane/styles.d.ts +1 -1
- package/dist/react/features/chat-pane/styles.js +156 -37
- package/dist/react/features/chat-pane/types.d.ts +17 -8
- package/dist/react/hooks/assistant-chats.js +12 -5
- package/dist/react/hooks/composer-draft-cache.js +116 -118
- package/dist/react/hooks/composer-history-storage.js +5 -5
- package/dist/react/hooks/fab-position.js +7 -4
- package/dist/react/hooks/useAnsweredCards.d.ts +21 -4
- package/dist/react/hooks/useAnsweredCards.js +38 -11
- package/dist/react/hooks/useArtifactStream.js +28 -22
- package/dist/react/hooks/useComposer.d.ts +20 -0
- package/dist/react/hooks/useComposer.js +22 -9
- package/dist/react/hooks/useComposerHistory.hooks.d.ts +6 -1
- package/dist/react/hooks/useComposerHistory.hooks.js +39 -19
- package/dist/react/hooks/useComposerHistoryKeys.hooks.js +24 -14
- package/dist/react/hooks/useConversation.d.ts +7 -0
- package/dist/react/hooks/useConversation.js +23 -10
- package/dist/react/hooks/useExtEventGroups.js +39 -39
- package/dist/react/hooks/useQuestionForms.js +21 -26
- package/dist/react/hooks/useRunStream.js +10 -2
- package/dist/react/index.d.ts +7 -1
- package/dist/react/index.js +9 -0
- package/dist/react/message-blocks.d.ts +6 -4
- package/dist/react/message-blocks.js +94 -72
- package/dist/react/slots.d.ts +2 -2
- package/dist/react/tool-call-display.js +19 -17
- package/dist/server/durable-runs/recover.d.ts +1 -1
- package/dist/server/durable-runs/recover.js +139 -76
- package/dist/server/run-finalizer/entry.js +25 -15
- package/dist/store/legacy/sqlite/conversations.d.ts +11 -11
- package/dist/store/legacy/sqlite/conversations.js +69 -98
- package/dist/store/legacy/sqlite/messages.d.ts +10 -2
- package/dist/store/legacy/sqlite/messages.js +113 -71
- package/dist/store/runtime/chat-history-store.js +9 -3
- package/dist/store/runtime/durable-run-store.d.ts +2 -2
- package/dist/store/runtime/durable-run-store.js +165 -117
- package/dist/store/runtime/history-tools.js +118 -82
- package/dist/store/runtime/run-ledger.d.ts +4 -1
- package/dist/store/runtime/run-ledger.js +56 -16
- package/dist/store/sql/cursor.js +38 -18
- package/dist/store/sql/store.d.ts +18 -0
- package/dist/store/sql/store.js +46 -15
- package/package.json +12 -10
|
@@ -44,12 +44,13 @@ export function useChatPaneControlsHeight() {
|
|
|
44
44
|
if (!root || !controls || typeof ResizeObserver !== 'function')
|
|
45
45
|
return;
|
|
46
46
|
const observer = new ResizeObserver((entries) => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
// Only `controls` is observed, so a notification carries at most one entry: `slice(0, 1)`
|
|
48
|
+
// reads it without a branch for the empty list the ResizeObserver spec never delivers.
|
|
49
|
+
for (const entry of entries.slice(0, 1)) {
|
|
50
|
+
// Rounded up: a fractional px short would let one glyph's descender clip against the
|
|
51
|
+
// gradient's opaque end, which `Math.ceil` never risks understating.
|
|
52
|
+
root.style.setProperty(CHAT_PANE_CONTROLS_HEIGHT_CSS_VAR, `${Math.ceil(entry.contentRect.height)}px`);
|
|
53
|
+
}
|
|
53
54
|
});
|
|
54
55
|
observer.observe(controls);
|
|
55
56
|
return () => observer.disconnect();
|
|
@@ -25,6 +25,32 @@ const NEW_TAB_PROTOCOLS = new Set(['http:', 'https:']);
|
|
|
25
25
|
function openInNewTabDefault(url) {
|
|
26
26
|
window.open(url, '_blank', 'noopener,noreferrer');
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Whether the click is a plain primary click nobody has handled yet: modified or non-primary
|
|
30
|
+
* clicks are left to the browser, which already opens those however the user asked.
|
|
31
|
+
*
|
|
32
|
+
* @param input.event - The native click.
|
|
33
|
+
* @returns `true` when the guard may take the click over.
|
|
34
|
+
*/
|
|
35
|
+
function isPlainPrimaryClick({ event }) {
|
|
36
|
+
if (event.defaultPrevented || event.button !== 0)
|
|
37
|
+
return false;
|
|
38
|
+
return !(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Whether an anchor is an ordinary in-tab web link the guard should redirect to a new tab — not
|
|
42
|
+
* one already opening elsewhere, a download, an action styled as a link, or a `#` same-document jump.
|
|
43
|
+
*
|
|
44
|
+
* @param input.link - An anchor matched by `a[href]`, so its `href` attribute is present.
|
|
45
|
+
* @returns `true` when following the link would navigate the chat's own tab to an http(s) page.
|
|
46
|
+
*/
|
|
47
|
+
function isInTabWebLink({ link }) {
|
|
48
|
+
if (link.target === '_blank' || link.hasAttribute('download') || link.getAttribute('role') === 'button')
|
|
49
|
+
return false;
|
|
50
|
+
if (link.getAttribute('href').startsWith('#'))
|
|
51
|
+
return false;
|
|
52
|
+
return NEW_TAB_PROTOCOLS.has(link.protocol);
|
|
53
|
+
}
|
|
28
54
|
/**
|
|
29
55
|
* Decides whether a click inside the pane should open a link in a new tab.
|
|
30
56
|
*
|
|
@@ -34,21 +60,13 @@ function openInNewTabDefault(url) {
|
|
|
34
60
|
* @complexity O(d) for `closest` over the target's ancestor depth d.
|
|
35
61
|
*/
|
|
36
62
|
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
63
|
const target = event.target;
|
|
42
|
-
if (!(target instanceof Element))
|
|
64
|
+
if (!isPlainPrimaryClick({ event }) || !(target instanceof Element))
|
|
43
65
|
return null;
|
|
44
66
|
const link = target.closest('a[href]');
|
|
45
67
|
if (!(link instanceof HTMLAnchorElement) || !root.contains(link))
|
|
46
68
|
return null;
|
|
47
|
-
|
|
48
|
-
return null;
|
|
49
|
-
if (link.getAttribute('href').startsWith('#'))
|
|
50
|
-
return null;
|
|
51
|
-
return NEW_TAB_PROTOCOLS.has(link.protocol) ? link.href : null;
|
|
69
|
+
return isInTabWebLink({ link }) ? link.href : null;
|
|
52
70
|
}
|
|
53
71
|
/**
|
|
54
72
|
* The pane root's `onClickCapture` handler: opens qualifying link clicks in a new tab and cancels
|
|
@@ -3,7 +3,7 @@ export interface UseChatPaneRuntimeInventoryOptions {
|
|
|
3
3
|
access?: ChatPaneRuntimeAccess;
|
|
4
4
|
initialAgents?: readonly ChatPaneAgent[];
|
|
5
5
|
pollIntervalMs?: number;
|
|
6
|
-
/**
|
|
6
|
+
/** Failure backoff; pending discovery doubles the final step until its finite window ends. */
|
|
7
7
|
retryDelaysMs?: readonly number[];
|
|
8
8
|
}
|
|
9
9
|
export interface UseChatPaneRuntimeInventoryResult {
|
|
@@ -30,13 +30,13 @@ export interface UseChatPaneRuntimeInventoryResult {
|
|
|
30
30
|
* daemon restarting under an open pane left it on "No usable CLI is selected" until a hard
|
|
31
31
|
* reload). Until the first successful answer the hook retries on a backoff, and immediately when
|
|
32
32
|
* the daemon goes from offline to online, the window regains focus, or the tab becomes visible.
|
|
33
|
-
* A RESOLVED list
|
|
33
|
+
* A RESOLVED list is an answer, including an empty one. Entries marked discoveryPending retry
|
|
34
|
+
* within a finite sweep window, honoring the host cache's next eligible refresh deadline.
|
|
34
35
|
*
|
|
35
36
|
* Health polling runs only while the tab is visible (hidden tabs made no-op status calls every
|
|
36
37
|
* `pollIntervalMs` forever), and never overlaps a status call still in flight.
|
|
37
38
|
*
|
|
38
39
|
* @complexity Time: O(n) per inventory response; space: O(n) for the snapshot.
|
|
39
|
-
* @overallScore 100/100
|
|
40
40
|
*/
|
|
41
41
|
export declare function useChatPaneRuntimeInventory({ access, initialAgents, pollIntervalMs, retryDelaysMs, }: UseChatPaneRuntimeInventoryOptions): UseChatPaneRuntimeInventoryResult;
|
|
42
42
|
//# sourceMappingURL=useChatPaneRuntimeInventory.hooks.d.ts.map
|
|
@@ -4,6 +4,11 @@ const EMPTY_AGENTS = [];
|
|
|
4
4
|
/** Waits between inventory retries while `listAgents` keeps failing: quick at first (a daemon
|
|
5
5
|
* restart takes seconds), then every 30s for as long as the pane stays mounted. */
|
|
6
6
|
const DEFAULT_RETRY_DELAYS_MS = [1_000, 2_000, 4_000, 8_000, 15_000, 30_000];
|
|
7
|
+
// The native host serializes metadata for 29 definitions: version/help/auth checks and up to
|
|
8
|
+
// 15s per catalog can take minutes in a cold sweep. Include queue time and a final cache cooldown
|
|
9
|
+
// margin, then stop; extending backoff beyond 30s keeps this to ten default retries, not polling.
|
|
10
|
+
const PENDING_DISCOVERY_WINDOW_MS = 15 * 60_000;
|
|
11
|
+
const PENDING_DISCOVERY_RETRY_LIMIT = 10;
|
|
7
12
|
/**
|
|
8
13
|
* Owns agent inventory, explicit rescans, and daemon-health polling while the
|
|
9
14
|
* host supplies only the environment-specific I/O effects.
|
|
@@ -15,13 +20,13 @@ const DEFAULT_RETRY_DELAYS_MS = [1_000, 2_000, 4_000, 8_000, 15_000, 30_000];
|
|
|
15
20
|
* daemon restarting under an open pane left it on "No usable CLI is selected" until a hard
|
|
16
21
|
* reload). Until the first successful answer the hook retries on a backoff, and immediately when
|
|
17
22
|
* the daemon goes from offline to online, the window regains focus, or the tab becomes visible.
|
|
18
|
-
* A RESOLVED list
|
|
23
|
+
* A RESOLVED list is an answer, including an empty one. Entries marked discoveryPending retry
|
|
24
|
+
* within a finite sweep window, honoring the host cache's next eligible refresh deadline.
|
|
19
25
|
*
|
|
20
26
|
* Health polling runs only while the tab is visible (hidden tabs made no-op status calls every
|
|
21
27
|
* `pollIntervalMs` forever), and never overlaps a status call still in flight.
|
|
22
28
|
*
|
|
23
29
|
* @complexity Time: O(n) per inventory response; space: O(n) for the snapshot.
|
|
24
|
-
* @overallScore 100/100
|
|
25
30
|
*/
|
|
26
31
|
export function useChatPaneRuntimeInventory({ access, initialAgents = EMPTY_AGENTS, pollIntervalMs = 5_000, retryDelaysMs = DEFAULT_RETRY_DELAYS_MS, }) {
|
|
27
32
|
const [agents, setAgents] = useState(initialAgents);
|
|
@@ -51,16 +56,19 @@ export function useChatPaneRuntimeInventory({ access, initialAgents = EMPTY_AGEN
|
|
|
51
56
|
if (!access)
|
|
52
57
|
return;
|
|
53
58
|
retry.cancel();
|
|
59
|
+
if (rescan)
|
|
60
|
+
retry.restart();
|
|
54
61
|
setScanningAgents(true);
|
|
55
62
|
setRuntimeInventoryError(null);
|
|
56
63
|
await inventory.run({ body: async (token) => {
|
|
57
64
|
const nextAgents = await (rescan ? access.rescanAgents() : access.listAgents());
|
|
58
65
|
token.ensureCurrent();
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
const pending = nextAgents.some(agent => agent.discoveryPending);
|
|
67
|
+
retry.answered({ pending });
|
|
68
|
+
if (pending)
|
|
69
|
+
retry.schedule(() => void loadAgents(false), {
|
|
70
|
+
pending: true, nextRefreshAt: access.getAgentsRefreshAt?.() ?? Date.now(),
|
|
71
|
+
});
|
|
64
72
|
setAgents([...nextAgents]);
|
|
65
73
|
setConnectingAgents(false);
|
|
66
74
|
setScanningAgents(false);
|
|
@@ -182,13 +190,15 @@ export function useChatPaneRuntimeInventory({ access, initialAgents = EMPTY_AGEN
|
|
|
182
190
|
}
|
|
183
191
|
/**
|
|
184
192
|
* The retry bookkeeping `useChatPaneRuntimeInventory` needs, in refs so none of it re-renders:
|
|
185
|
-
* whether an answer has arrived, the
|
|
186
|
-
* result (`null` until the first
|
|
193
|
+
* whether an answer has arrived, the retry timer, separate failure/discovery steps, a finite
|
|
194
|
+
* discovery deadline, and the last health result (`null` until the first health response).
|
|
187
195
|
*/
|
|
188
196
|
function useInventoryRetry(delaysMs) {
|
|
189
197
|
const answeredRef = useRef(false);
|
|
190
198
|
const timerRef = useRef(undefined);
|
|
191
199
|
const stepRef = useRef(0);
|
|
200
|
+
const pendingStepRef = useRef(0);
|
|
201
|
+
const pendingDeadlineRef = useRef(undefined);
|
|
192
202
|
const lastOnlineRef = useRef(null);
|
|
193
203
|
const delaysRef = useRef(delaysMs);
|
|
194
204
|
delaysRef.current = delaysMs;
|
|
@@ -200,15 +210,33 @@ function useInventoryRetry(delaysMs) {
|
|
|
200
210
|
const restart = () => {
|
|
201
211
|
cancel();
|
|
202
212
|
stepRef.current = 0;
|
|
213
|
+
pendingStepRef.current = 0;
|
|
214
|
+
pendingDeadlineRef.current = undefined;
|
|
215
|
+
};
|
|
216
|
+
const schedulePending = (attempt, nextRefreshAt) => {
|
|
217
|
+
const now = Date.now();
|
|
218
|
+
pendingDeadlineRef.current ??= now + PENDING_DISCOVERY_WINDOW_MS;
|
|
219
|
+
const delay = pendingRetryDelayMs({
|
|
220
|
+
delaysMs: delaysRef.current, step: pendingStepRef.current, now, deadline: pendingDeadlineRef.current, nextRefreshAt,
|
|
221
|
+
});
|
|
222
|
+
if (delay === null)
|
|
223
|
+
return;
|
|
224
|
+
pendingStepRef.current += 1;
|
|
225
|
+
timerRef.current = window.setTimeout(attempt, delay);
|
|
203
226
|
};
|
|
204
227
|
return {
|
|
205
228
|
hasAnswer: () => answeredRef.current,
|
|
206
|
-
answered: () => {
|
|
229
|
+
answered: ({ pending }) => {
|
|
207
230
|
answeredRef.current = true;
|
|
208
|
-
|
|
231
|
+
if (!pending)
|
|
232
|
+
restart();
|
|
209
233
|
},
|
|
210
|
-
schedule: (attempt) => {
|
|
234
|
+
schedule: (attempt, { pending = false, nextRefreshAt = 0 } = {}) => {
|
|
211
235
|
cancel();
|
|
236
|
+
if (pending) {
|
|
237
|
+
schedulePending(attempt, nextRefreshAt);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
212
240
|
const delays = delaysRef.current;
|
|
213
241
|
const delay = delays[Math.min(stepRef.current, delays.length - 1)] ?? 0;
|
|
214
242
|
stepRef.current += 1;
|
|
@@ -230,4 +258,24 @@ function useInventoryRetry(delaysMs) {
|
|
|
230
258
|
});
|
|
231
259
|
return retry;
|
|
232
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* The wait before the next pending-discovery retry: the backoff step (doubling the final step once
|
|
263
|
+
* the list runs out), pushed out to the host cache's next eligible refresh, capped at the window.
|
|
264
|
+
*
|
|
265
|
+
* @param input.delaysMs - Failure backoff steps; empty means no pending retries at all.
|
|
266
|
+
* @param input.step - Pending retries already made in this discovery window.
|
|
267
|
+
* @param input.now - Current epoch ms.
|
|
268
|
+
* @param input.deadline - Epoch ms the finite discovery window closes.
|
|
269
|
+
* @param input.nextRefreshAt - Earliest epoch ms the host cache can refresh.
|
|
270
|
+
* @returns Milliseconds to wait, or `null` when the retry budget or window is spent.
|
|
271
|
+
* @complexity O(1).
|
|
272
|
+
*/
|
|
273
|
+
function pendingRetryDelayMs({ delaysMs, step, now, deadline, nextRefreshAt }) {
|
|
274
|
+
const lastDelay = delaysMs[delaysMs.length - 1];
|
|
275
|
+
if (lastDelay === undefined || step >= PENDING_DISCOVERY_RETRY_LIMIT || now >= deadline)
|
|
276
|
+
return null;
|
|
277
|
+
const delay = delaysMs[step] ?? lastDelay * 2 ** (step - delaysMs.length + 1);
|
|
278
|
+
const refreshAt = Math.max(nextRefreshAt, Math.min(now + delay, deadline));
|
|
279
|
+
return refreshAt > deadline ? null : refreshAt - now;
|
|
280
|
+
}
|
|
233
281
|
//# sourceMappingURL=useChatPaneRuntimeInventory.hooks.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mechanics for the composer bar's small model / reasoning-effort menus ("drop-ups"): open/close,
|
|
3
|
+
* outside-click and Escape dismissal, a viewport-fixed position anchored ABOVE the trigger, focus
|
|
4
|
+
* on open and back to the trigger on close, and arrow-key roving between the items.
|
|
5
|
+
*
|
|
6
|
+
* Same idiom as `useAgentRuntimePicker` (whose pure `resolveRovingIndex` this reuses), minus what a
|
|
7
|
+
* flat list of choices does not need: no portal (the menu renders inline, so it inherits the
|
|
8
|
+
* host's `data-theme` and the pane's scoped CSS; `position: fixed` still escapes the dock's
|
|
9
|
+
* `overflow: hidden`, as `composerDiscoveryMenuPosition` documents) and no Tab trap (Tab simply
|
|
10
|
+
* closes the menu and lets focus move on).
|
|
11
|
+
*/
|
|
12
|
+
import { type CSSProperties, type KeyboardEvent as ReactKeyboardEvent, type RefObject } from 'react';
|
|
13
|
+
/**
|
|
14
|
+
* Viewport-fixed geometry for a drop-up: right-aligned with the trigger, clamped into the
|
|
15
|
+
* viewport and (when given) into `bounds`, bottom edge just above it, and never taller than the
|
|
16
|
+
* space above. Right-aligned because the model/effort cluster sits at the composer bar's trailing
|
|
17
|
+
* edge, so the menu grows back into the pane instead of past the dock's right edge; `bounds` (the
|
|
18
|
+
* composer box) keeps a menu wider than the room left of the trigger from spilling out of a narrow
|
|
19
|
+
* dock onto the page beside it.
|
|
20
|
+
*
|
|
21
|
+
* @complexity Time/space: O(1).
|
|
22
|
+
*/
|
|
23
|
+
export declare function runtimeDropUpPosition({ trigger, width }: {
|
|
24
|
+
trigger: DOMRect;
|
|
25
|
+
width: number;
|
|
26
|
+
}, { bounds }?: {
|
|
27
|
+
bounds?: Pick<DOMRect, 'left' | 'right'>;
|
|
28
|
+
}): CSSProperties;
|
|
29
|
+
export interface UseRuntimeDropUpResult {
|
|
30
|
+
menuId: string;
|
|
31
|
+
open: boolean;
|
|
32
|
+
/** Undefined until the trigger has been measured; the menu does not render before then. */
|
|
33
|
+
position: CSSProperties | undefined;
|
|
34
|
+
triggerRef: RefObject<HTMLButtonElement | null>;
|
|
35
|
+
menuRef: RefObject<HTMLDivElement | null>;
|
|
36
|
+
toggleOpen: () => void;
|
|
37
|
+
/** ArrowUp/ArrowDown on the closed trigger opens the menu, like a native select. */
|
|
38
|
+
handleTriggerKeyDown: (event: ReactKeyboardEvent<HTMLButtonElement>) => void;
|
|
39
|
+
handleMenuKeyDown: (event: ReactKeyboardEvent<HTMLDivElement>) => void;
|
|
40
|
+
/** Runs the selection, closes, and returns focus to the trigger. */
|
|
41
|
+
choose: (apply: () => void) => void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* @complexity Time: O(1) per render; key handling is O(n) in the menu's item count.
|
|
45
|
+
*/
|
|
46
|
+
export declare function useRuntimeDropUp({ width }: {
|
|
47
|
+
width: number;
|
|
48
|
+
}): UseRuntimeDropUpResult;
|
|
49
|
+
//# sourceMappingURL=useRuntimeDropUp.hooks.d.ts.map
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mechanics for the composer bar's small model / reasoning-effort menus ("drop-ups"): open/close,
|
|
3
|
+
* outside-click and Escape dismissal, a viewport-fixed position anchored ABOVE the trigger, focus
|
|
4
|
+
* on open and back to the trigger on close, and arrow-key roving between the items.
|
|
5
|
+
*
|
|
6
|
+
* Same idiom as `useAgentRuntimePicker` (whose pure `resolveRovingIndex` this reuses), minus what a
|
|
7
|
+
* flat list of choices does not need: no portal (the menu renders inline, so it inherits the
|
|
8
|
+
* host's `data-theme` and the pane's scoped CSS; `position: fixed` still escapes the dock's
|
|
9
|
+
* `overflow: hidden`, as `composerDiscoveryMenuPosition` documents) and no Tab trap (Tab simply
|
|
10
|
+
* closes the menu and lets focus move on).
|
|
11
|
+
*/
|
|
12
|
+
import { useCallback, useEffect, useId, useRef, useState, } from 'react';
|
|
13
|
+
import { resolveRovingIndex } from './useAgentRuntimePicker.hooks.js';
|
|
14
|
+
const MENU_ITEMS = '[role="menuitemradio"]';
|
|
15
|
+
/**
|
|
16
|
+
* Viewport-fixed geometry for a drop-up: right-aligned with the trigger, clamped into the
|
|
17
|
+
* viewport and (when given) into `bounds`, bottom edge just above it, and never taller than the
|
|
18
|
+
* space above. Right-aligned because the model/effort cluster sits at the composer bar's trailing
|
|
19
|
+
* edge, so the menu grows back into the pane instead of past the dock's right edge; `bounds` (the
|
|
20
|
+
* composer box) keeps a menu wider than the room left of the trigger from spilling out of a narrow
|
|
21
|
+
* dock onto the page beside it.
|
|
22
|
+
*
|
|
23
|
+
* @complexity Time/space: O(1).
|
|
24
|
+
*/
|
|
25
|
+
export function runtimeDropUpPosition({ trigger, width }, { bounds } = {}) {
|
|
26
|
+
const margin = 12;
|
|
27
|
+
const gap = 6;
|
|
28
|
+
const minLeft = Math.max(margin, bounds?.left ?? -Infinity);
|
|
29
|
+
const maxRight = Math.min(window.innerWidth - margin, bounds?.right ?? Infinity);
|
|
30
|
+
const menuWidth = Math.max(0, Math.min(width, maxRight - minLeft));
|
|
31
|
+
return {
|
|
32
|
+
position: 'fixed',
|
|
33
|
+
left: Math.min(Math.max(trigger.right - menuWidth, minLeft), maxRight - menuWidth),
|
|
34
|
+
bottom: Math.max(margin, window.innerHeight - trigger.top + gap),
|
|
35
|
+
width: menuWidth,
|
|
36
|
+
maxHeight: Math.min(320, Math.max(0, trigger.top - gap - margin)),
|
|
37
|
+
overflowY: 'auto',
|
|
38
|
+
zIndex: 1000,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* @complexity Time: O(1) per render; key handling is O(n) in the menu's item count.
|
|
43
|
+
*/
|
|
44
|
+
export function useRuntimeDropUp({ width }) {
|
|
45
|
+
const menuId = useId();
|
|
46
|
+
const [open, setOpen] = useState(false);
|
|
47
|
+
const [position, setPosition] = useState();
|
|
48
|
+
const triggerRef = useRef(null);
|
|
49
|
+
const menuRef = useRef(null);
|
|
50
|
+
const closeToTrigger = useCallback(() => {
|
|
51
|
+
setOpen(false);
|
|
52
|
+
triggerRef.current?.focus();
|
|
53
|
+
}, []);
|
|
54
|
+
const toggleOpen = useCallback(() => {
|
|
55
|
+
setOpen((current) => !current);
|
|
56
|
+
}, []);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!open)
|
|
59
|
+
return;
|
|
60
|
+
const closeOnOutsidePointer = (event) => {
|
|
61
|
+
const target = event.target;
|
|
62
|
+
if (triggerRef.current?.contains(target) || menuRef.current?.contains(target))
|
|
63
|
+
return;
|
|
64
|
+
// No focus restore: the pointer is already going somewhere else.
|
|
65
|
+
setOpen(false);
|
|
66
|
+
};
|
|
67
|
+
const closeOnEscape = (event) => {
|
|
68
|
+
if (event.key === 'Escape')
|
|
69
|
+
closeToTrigger();
|
|
70
|
+
};
|
|
71
|
+
document.addEventListener('mousedown', closeOnOutsidePointer);
|
|
72
|
+
document.addEventListener('keydown', closeOnEscape);
|
|
73
|
+
return () => {
|
|
74
|
+
document.removeEventListener('mousedown', closeOnOutsidePointer);
|
|
75
|
+
document.removeEventListener('keydown', closeOnEscape);
|
|
76
|
+
};
|
|
77
|
+
}, [closeToTrigger, open]);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!open)
|
|
80
|
+
return;
|
|
81
|
+
const update = () => {
|
|
82
|
+
const rect = triggerRef.current?.getBoundingClientRect();
|
|
83
|
+
const bounds = triggerRef.current?.closest('.jini-composer')?.getBoundingClientRect();
|
|
84
|
+
if (rect)
|
|
85
|
+
setPosition(runtimeDropUpPosition({ trigger: rect, width }, bounds ? { bounds } : {}));
|
|
86
|
+
};
|
|
87
|
+
update();
|
|
88
|
+
window.addEventListener('resize', update);
|
|
89
|
+
// Capture phase: a scroll inside any ancestor moves the trigger too, and scroll does not bubble.
|
|
90
|
+
window.addEventListener('scroll', update, true);
|
|
91
|
+
return () => {
|
|
92
|
+
window.removeEventListener('resize', update);
|
|
93
|
+
window.removeEventListener('scroll', update, true);
|
|
94
|
+
};
|
|
95
|
+
}, [open, width]);
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (!open || !position)
|
|
98
|
+
return;
|
|
99
|
+
const menu = menuRef.current;
|
|
100
|
+
const checked = menu?.querySelector(`${MENU_ITEMS}[aria-checked="true"]`);
|
|
101
|
+
(checked ?? menu?.querySelector(MENU_ITEMS))?.focus();
|
|
102
|
+
}, [open, position]);
|
|
103
|
+
const handleTriggerKeyDown = useCallback((event) => {
|
|
104
|
+
if (open || (event.key !== 'ArrowUp' && event.key !== 'ArrowDown'))
|
|
105
|
+
return;
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
setOpen(true);
|
|
108
|
+
}, [open]);
|
|
109
|
+
const handleMenuKeyDown = useCallback((event) => {
|
|
110
|
+
if (event.key === 'Tab') {
|
|
111
|
+
setOpen(false);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
// Only reachable via the element `menuRef` is attached to, so the ref is set by now; and the
|
|
115
|
+
// menu is never rendered without items (the caller omits a drop-up that has no choices).
|
|
116
|
+
const items = [...menuRef.current.querySelectorAll(MENU_ITEMS)];
|
|
117
|
+
const next = resolveRovingIndex({
|
|
118
|
+
key: event.key,
|
|
119
|
+
currentIndex: items.indexOf(document.activeElement),
|
|
120
|
+
count: items.length,
|
|
121
|
+
});
|
|
122
|
+
if (next === null)
|
|
123
|
+
return;
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
items[next]?.focus();
|
|
126
|
+
}, []);
|
|
127
|
+
const choose = useCallback((apply) => {
|
|
128
|
+
apply();
|
|
129
|
+
closeToTrigger();
|
|
130
|
+
}, [closeToTrigger]);
|
|
131
|
+
return {
|
|
132
|
+
menuId,
|
|
133
|
+
open,
|
|
134
|
+
position,
|
|
135
|
+
triggerRef,
|
|
136
|
+
menuRef,
|
|
137
|
+
toggleOpen,
|
|
138
|
+
handleTriggerKeyDown,
|
|
139
|
+
handleMenuKeyDown,
|
|
140
|
+
choose,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=useRuntimeDropUp.hooks.js.map
|
|
@@ -2,11 +2,28 @@ import type { I18nAdapter } from '../../slots.js';
|
|
|
2
2
|
import type { ChatPaneAgent, ChatPaneAgentOption } from './types.js';
|
|
3
3
|
export declare function isConcretePickerModel(id: string): boolean;
|
|
4
4
|
/** Aliases are selectable only as their evidence-backed concrete ID. Never render a Default row.
|
|
5
|
-
*
|
|
5
|
+
* Keeps the first position per concrete ID, upgrading raw-ID labels from later human labels.
|
|
6
|
+
* Legacy Default labels are rewritten to the ID before deduplication, never used as human labels.
|
|
7
|
+
* @returns Distinct concrete picker rows without changing the source catalog. No side effects or errors.
|
|
8
|
+
* @complexity O(n) time and space in catalog rows, using insertion-ordered Map keys.
|
|
6
9
|
*/
|
|
7
10
|
export declare function pickerModelOptions(agent: ChatPaneAgent): ChatPaneAgentOption[];
|
|
8
11
|
export declare function configuredPickerModel(agent: ChatPaneAgent): string | undefined;
|
|
9
12
|
export declare function pickerModelLabel(agent: ChatPaneAgent | undefined, requested?: string, t?: I18nAdapter['t']): string;
|
|
10
13
|
export declare function pickerModelValue(agent: ChatPaneAgent, requested?: string): string;
|
|
11
14
|
export declare function pickerCatalogNote(agent: ChatPaneAgent, t?: I18nAdapter['t']): string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* The composer bar's compact model name: the catalog label (or a prettified raw id when the catalog
|
|
17
|
+
* has no label), minus the leading vendor word the agent's icon already conveys, capped with "…".
|
|
18
|
+
* The full label stays in the drop-up list and the trigger's tooltip.
|
|
19
|
+
* @example shortModelLabel({ label: 'GPT-6.1-Sol', agentId: 'codex' }) // "6.1 Sol"
|
|
20
|
+
* @example shortModelLabel({ label: 'claude-opus-5-5', agentId: 'claude' }) // "Opus 5.5"
|
|
21
|
+
* @complexity O(n) in the label length. Pure.
|
|
22
|
+
*/
|
|
23
|
+
export declare function shortModelLabel({ label, agentId }: {
|
|
24
|
+
label: string;
|
|
25
|
+
agentId?: string | undefined;
|
|
26
|
+
}, { max }?: {
|
|
27
|
+
max?: number;
|
|
28
|
+
}): string;
|
|
12
29
|
//# sourceMappingURL=model-options.d.ts.map
|
|
@@ -1,30 +1,17 @@
|
|
|
1
|
+
import { concreteModelOptions, modelIdentityKind } from '@jini-ai/agent-runtime/model-selection';
|
|
1
2
|
// Model names and timestamps are metadata; only surrounding picker copy is translated.
|
|
2
3
|
const passthrough = key => key;
|
|
3
4
|
export function isConcretePickerModel(id) {
|
|
4
|
-
|
|
5
|
-
return !/^(?:opus|sonnet|haiku|fable|best|latest|claude|gemini|gpt|codex|swe)(?:\[.*\])?$/i.test(token) && !/^(default|auto|adaptive|smart|deep|rush|ultimate|premium|balanced|fast|grok-build|vercel-ai-gateway)$/i.test(token) && !/(?:^|[-/])(gateway|latest)$/i.test(id);
|
|
5
|
+
return modelIdentityKind(id) === 'concrete';
|
|
6
6
|
}
|
|
7
7
|
/** Aliases are selectable only as their evidence-backed concrete ID. Never render a Default row.
|
|
8
|
-
*
|
|
8
|
+
* Keeps the first position per concrete ID, upgrading raw-ID labels from later human labels.
|
|
9
|
+
* Legacy Default labels are rewritten to the ID before deduplication, never used as human labels.
|
|
10
|
+
* @returns Distinct concrete picker rows without changing the source catalog. No side effects or errors.
|
|
11
|
+
* @complexity O(n) time and space in catalog rows, using insertion-ordered Map keys.
|
|
9
12
|
*/
|
|
10
13
|
export function pickerModelOptions(agent) {
|
|
11
|
-
|
|
12
|
-
return [];
|
|
13
|
-
const seen = new Set();
|
|
14
|
-
const options = (agent.models || []).flatMap((row) => {
|
|
15
|
-
const id = row.resolvedId || (row.identityKind === 'routing-mode' || row.identityKind === 'alias' ? undefined : row.id);
|
|
16
|
-
if (!id || !isConcretePickerModel(id) || seen.has(id))
|
|
17
|
-
return [];
|
|
18
|
-
seen.add(id);
|
|
19
|
-
// Older cached inventories can attach a default-policy label to a resolved concrete ID.
|
|
20
|
-
const label = row.id === 'default' || /^default\b/i.test(row.label) ? id : row.label;
|
|
21
|
-
return [{ ...row, id, label, identityKind: 'concrete' }];
|
|
22
|
-
});
|
|
23
|
-
const resolution = agent.defaultModelResolution;
|
|
24
|
-
// A host may publish a resolved default outside its visible catalog. Preserve its concrete ID.
|
|
25
|
-
if (resolution?.status === 'resolved' && isConcretePickerModel(resolution.id) && !seen.has(resolution.id))
|
|
26
|
-
options.unshift({ id: resolution.id, label: resolution.id, identityKind: 'concrete' });
|
|
27
|
-
return options;
|
|
14
|
+
return concreteModelOptions(agent);
|
|
28
15
|
}
|
|
29
16
|
export function configuredPickerModel(agent) {
|
|
30
17
|
if (agent.supportsConcreteModelSelection === false)
|
|
@@ -52,4 +39,46 @@ export function pickerCatalogNote(agent, t = passthrough) {
|
|
|
52
39
|
return t('Offline fallback');
|
|
53
40
|
return undefined;
|
|
54
41
|
}
|
|
42
|
+
// The vendor word each CLI's own icon already shows beside the bar label (owner 2026-10-18).
|
|
43
|
+
// Agents not listed keep every word: a Cursor icon says nothing about whether a model is GPT.
|
|
44
|
+
const BAR_VENDOR_WORDS = {
|
|
45
|
+
claude: ['claude', 'anthropic'],
|
|
46
|
+
codex: ['gpt', 'openai'],
|
|
47
|
+
antigravity: ['gemini', 'google'],
|
|
48
|
+
};
|
|
49
|
+
// A bare catalog id ("claude-opus-5-5", "gpt-6-astra[1m]", "anthropic/claude-sonnet-5") rather than a display name.
|
|
50
|
+
const RAW_MODEL_ID = /^(?:[a-z0-9._-]+\/)*[a-z0-9][a-z0-9._-]*(?:\[[^\]]*\])?$/;
|
|
51
|
+
const BRAND_CASING = { gpt: 'GPT' };
|
|
52
|
+
function prettifyRawModelId(id) {
|
|
53
|
+
// A routing prefix ("anthropic/") is the full name's job; the bar names only the model.
|
|
54
|
+
const tokens = id.slice(id.lastIndexOf('/') + 1).replace(/\[[^\]]*\]$/, '').split(/[-_]+/).filter((token) => token && !/^\d{8}$/.test(token));
|
|
55
|
+
const words = [];
|
|
56
|
+
for (const token of tokens) {
|
|
57
|
+
const previous = words[words.length - 1];
|
|
58
|
+
// Dash-separated version digits read as one dotted version: "5-5" → "5.5".
|
|
59
|
+
if (/^\d+$/.test(token) && previous && /^\d+(?:\.\d+)*$/.test(previous))
|
|
60
|
+
words[words.length - 1] = `${previous}.${token}`;
|
|
61
|
+
else
|
|
62
|
+
words.push(BRAND_CASING[token] ?? token.charAt(0).toUpperCase() + token.slice(1));
|
|
63
|
+
}
|
|
64
|
+
return words;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The composer bar's compact model name: the catalog label (or a prettified raw id when the catalog
|
|
68
|
+
* has no label), minus the leading vendor word the agent's icon already conveys, capped with "…".
|
|
69
|
+
* The full label stays in the drop-up list and the trigger's tooltip.
|
|
70
|
+
* @example shortModelLabel({ label: 'GPT-6.1-Sol', agentId: 'codex' }) // "6.1 Sol"
|
|
71
|
+
* @example shortModelLabel({ label: 'claude-opus-5-5', agentId: 'claude' }) // "Opus 5.5"
|
|
72
|
+
* @complexity O(n) in the label length. Pure.
|
|
73
|
+
*/
|
|
74
|
+
export function shortModelLabel({ label, agentId }, { max = 16 } = {}) {
|
|
75
|
+
const trimmed = label.trim();
|
|
76
|
+
const raw = RAW_MODEL_ID.test(trimmed);
|
|
77
|
+
const words = raw ? prettifyRawModelId(trimmed) : trimmed.split(/[\s-]+/).filter(Boolean);
|
|
78
|
+
const vendors = (agentId && BAR_VENDOR_WORDS[agentId]) || [];
|
|
79
|
+
const dropVendor = words.length > 1 && vendors.includes(words[0].toLowerCase());
|
|
80
|
+
// A display name with no vendor word to drop is shown as the catalog wrote it.
|
|
81
|
+
const short = dropVendor ? words.slice(1).join(' ') : raw ? words.join(' ') : trimmed;
|
|
82
|
+
return short.length > max ? `${short.slice(0, max - 1).trimEnd()}…` : short;
|
|
83
|
+
}
|
|
55
84
|
//# sourceMappingURL=model-options.js.map
|
|
@@ -108,7 +108,7 @@ export declare function isChatPaneQueueableBlocker({ blocker }: {
|
|
|
108
108
|
*
|
|
109
109
|
* A surface is attributed to the newest call still open when it arrived — the same rule
|
|
110
110
|
* `useExtEventGroups` uses to give a card its `call` — so an older call returning does not settle
|
|
111
|
-
* it.
|
|
111
|
+
* it. A terminal run, a later turn, a returned call or an accepted answer receipt closes it.
|
|
112
112
|
*
|
|
113
113
|
* @complexity O(e·c): e events in the newest message, c calls open at once (one or two in practice).
|
|
114
114
|
*/
|
|
@@ -118,8 +118,7 @@ export declare function isAwaitingTypedAnswer({ messages, toolName }: {
|
|
|
118
118
|
}): boolean;
|
|
119
119
|
/**
|
|
120
120
|
* The tool-use id of the question {@link isAwaitingTypedAnswer} sees waiting, or `null` — the newest
|
|
121
|
-
* one when several are open.
|
|
122
|
-
* for a question stays tied to THAT question after it closes (see `useChatPane`'s held answer).
|
|
121
|
+
* one when several are open. Only an active assistant run can hold a question open.
|
|
123
122
|
*
|
|
124
123
|
* @complexity O(e·c), as {@link isAwaitingTypedAnswer}.
|
|
125
124
|
*/
|
|
@@ -143,20 +142,20 @@ export declare function isTypedAnswerTurn({ blocker, attachmentCount, messages,
|
|
|
143
142
|
}): boolean;
|
|
144
143
|
/**
|
|
145
144
|
* The run a composer send can go INTO instead of queueing behind, or `null`: a run is streaming
|
|
146
|
-
* ({@link isChatPaneQueueableBlocker}), the
|
|
147
|
-
* only — the live agent's stdin takes no files), and the newest message is that run's assistant
|
|
145
|
+
* ({@link isChatPaneQueueableBlocker}), and the newest message is that run's assistant
|
|
148
146
|
* turn with a known `runId`.
|
|
147
|
+
* Attachment support is decided by the host's delivery, never by dropping them here.
|
|
149
148
|
*
|
|
150
149
|
* @complexity O(1).
|
|
151
150
|
*/
|
|
152
|
-
export declare function findMidRunMessageRunId({ blocker,
|
|
151
|
+
export declare function findMidRunMessageRunId({ blocker, messages }: {
|
|
153
152
|
blocker: ChatPaneSendBlocker | null;
|
|
154
153
|
attachmentCount: number;
|
|
155
154
|
messages: readonly ChatMessage[];
|
|
156
155
|
}): string | null;
|
|
157
156
|
/**
|
|
158
|
-
*
|
|
159
|
-
*
|
|
157
|
+
* MOVE: Display is per-message; delivery is ONE queued turn. A second message behind the same run
|
|
158
|
+
* is appended to that delivery (a blank line apart) rather than replacing it:
|
|
160
159
|
* replacing silently dropped the first message (owner, 2026-10-10). One turn, not two, because both
|
|
161
160
|
* were written for the run still going and the agent should read them together — two queued turns
|
|
162
161
|
* would also start two paid runs back to back.
|
|
@@ -166,7 +165,7 @@ export declare function findMidRunMessageRunId({ blocker, attachmentCount, messa
|
|
|
166
165
|
export declare function appendQueuedTurn({ queued, next }: {
|
|
167
166
|
queued: UserTextRedaction | null;
|
|
168
167
|
next: UserTextRedaction;
|
|
169
|
-
}): UserTextRedaction;
|
|
168
|
+
}, _options?: {}): UserTextRedaction;
|
|
170
169
|
/** The English copy (and i18n key) for why a typed answer was not sent. */
|
|
171
170
|
export declare function describeTypedAnswerNotice({ notice }: {
|
|
172
171
|
notice: TypedAnswerNotice;
|