@jini-ai/chat 0.6.15 → 0.6.16
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/README.md +6 -1
- package/dist/core/antigravity/tool-presentation.d.ts +13 -0
- package/dist/core/antigravity/tool-presentation.js +20 -0
- package/dist/core/assistant-client/ag-ui-transport.js +13 -1
- package/dist/core/events.d.ts +5 -1
- package/dist/core/tool-events.d.ts +18 -8
- package/dist/core/tool-events.js +72 -16
- package/dist/core/tool-presentation.d.ts +14 -0
- package/dist/core/tool-presentation.js +8 -0
- package/dist/core/user-text-redaction.d.ts +2 -1
- package/dist/core/user-text-redaction.js +10 -3
- package/dist/embed-widget/browser/frontend-session-bridge.d.ts +99 -0
- package/dist/embed-widget/browser/frontend-session-bridge.js +384 -0
- package/dist/embed-widget/browser/session-store.d.ts +3 -9
- package/dist/embed-widget/browser/session-store.js +6 -38
- package/dist/embed-widget/react/embed.d.ts +3 -10
- package/dist/embed-widget/react/embed.js +8 -52
- package/dist/embed-widget/vanilla/index.d.ts +0 -5
- package/dist/embed-widget/vanilla/index.js +4 -14
- package/dist/embed-widget/vanilla/state.d.ts +3 -11
- package/dist/embed-widget/vanilla/state.js +6 -37
- package/dist/embed-widget/vanilla/style.css +1 -1
- package/dist/react/agent-bridge/frontend-session-bridge.d.ts +2 -56
- package/dist/react/agent-bridge/frontend-session-bridge.js +2 -248
- package/dist/react/components/Icon.d.ts +1 -1
- package/dist/react/components/Icon.js +1 -0
- package/dist/react/components/Markdown.d.ts +1 -0
- package/dist/react/components/Markdown.js +1 -1
- package/dist/react/components/MessageList.d.ts +1 -1
- package/dist/react/components/MessageRow.d.ts +3 -0
- package/dist/react/components/MessageRow.js +10 -8
- package/dist/react/components/ToolCard.d.ts +3 -1
- package/dist/react/components/ToolCard.js +77 -58
- package/dist/react/components/ToolCard.rules.d.ts +20 -0
- package/dist/react/components/ToolCard.rules.js +92 -0
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +18 -6
- package/dist/react/features/chat-pane/components/ChatPane.js +3 -3
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +3 -1
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +9 -3
- package/dist/react/features/chat-pane/styles.d.ts +1 -1
- package/dist/react/features/chat-pane/styles.js +76 -9
- package/dist/react/features/chat-pane/types.d.ts +3 -0
- package/dist/react/hooks/useUserTextGuard.d.ts +1 -0
- package/dist/react/hooks/useUserTextGuard.js +3 -1
- package/dist/react/tool-call-display.d.ts +11 -0
- package/dist/react/tool-call-display.js +59 -2
- package/package.json +91 -86
- package/dist/core/browser/page-actions.d.ts +0 -121
- package/dist/core/browser/page-actions.js +0 -115
- package/dist/embed-widget/browser/page-actions.d.ts +0 -2
- package/dist/embed-widget/browser/page-actions.js +0 -3
|
@@ -71,7 +71,7 @@ import { Markdown } from './Markdown.js';
|
|
|
71
71
|
import { ToolCard } from './ToolCard.js';
|
|
72
72
|
import { QuestionForm } from './QuestionForm.js';
|
|
73
73
|
import { ToolCallGroup } from './ToolCallGroup.js';
|
|
74
|
-
import { applyToolCallDisplay, toolRowsForDisplay } from '../tool-call-display.js';
|
|
74
|
+
import { applyToolCallDisplay, retriedToolRowIds, toolRowsForDisplay } from '../tool-call-display.js';
|
|
75
75
|
/**
|
|
76
76
|
* True while an assistant message is still fully pending — no visible text
|
|
77
77
|
* and no tool rows have arrived yet. Shared by the "Thinking…" placeholder
|
|
@@ -157,9 +157,9 @@ function ThinkingBlock({ text }) {
|
|
|
157
157
|
function messageAwaitingAnswer({ runStreaming, runInProgress, events }, { isAnsweredCard } = {}) {
|
|
158
158
|
return runStreaming === true && runInProgress && isAwaitingAnswer({ events }, { isAnsweredCard });
|
|
159
159
|
}
|
|
160
|
-
function AssistantMessageFooter({ message, usageEvent, showUsageTotal, terminalNotice, pending, runInProgress, runStreaming, runSending, visibleContent, isAnsweredCard }, _optional = {}) {
|
|
160
|
+
function AssistantMessageFooter({ message, usageEvent, showUsageTotal, terminalNotice, pending, runInProgress, runStreaming, runSending, sendingLabel, visibleContent, isAnsweredCard }, _optional = {}) {
|
|
161
161
|
const t = useT();
|
|
162
|
-
return _jsxs(_Fragment, { children: [usageEvent ? _jsx(UsageSummary, { usage: usageEvent, showTotal: showUsageTotal, message: message, awaitingAnswer: isAwaitingAnswer({ events: message.events }, { isAnsweredCard }) }) : null, terminalNotice ? _jsx("div", { className: "jini-message-error", role: "status", children: t(terminalNotice) }) : null, !pending && !runInProgress ? (_jsx("div", { className: "jini-message-actions jini-message-actions--assistant", children: _jsx(CopyMessageButton, { text: visibleContent, label: t('Copy message') }) })) : null, runInProgress ? _jsx(RunActivityLine, { events: message.events, active: pending || (runStreaming === true && runInProgress), isAnsweredCard: isAnsweredCard, sending: runSending }) : null] });
|
|
162
|
+
return _jsxs(_Fragment, { children: [usageEvent ? _jsx(UsageSummary, { usage: usageEvent, showTotal: showUsageTotal, message: message, awaitingAnswer: isAwaitingAnswer({ events: message.events }, { isAnsweredCard }) }) : null, terminalNotice ? _jsx("div", { className: "jini-message-error", role: "status", children: t(terminalNotice) }) : null, !pending && !runInProgress ? (_jsx("div", { className: "jini-message-actions jini-message-actions--assistant", children: _jsx(CopyMessageButton, { text: visibleContent, label: t('Copy message') }) })) : null, runInProgress ? _jsx(RunActivityLine, { events: message.events, active: pending || (runStreaming === true && runInProgress), isAnsweredCard: isAnsweredCard, sending: runSending, sendingLabel: sendingLabel }) : null] });
|
|
163
163
|
}
|
|
164
164
|
/** Stable empty list for a message with no attachments. */
|
|
165
165
|
const NO_ATTACHMENTS = [];
|
|
@@ -186,7 +186,7 @@ function UserMessageRow({ message, renderAttachment }) {
|
|
|
186
186
|
const gallery = useAttachmentGallery({ attachments: message.attachments ?? NO_ATTACHMENTS });
|
|
187
187
|
return (_jsxs("div", { className: "jini-message jini-message-user", "data-message-id": message.id, "data-agent-element": `chat-message-${message.id}`, "data-agent-role": "region", "data-agent-label": "A message from the user", children: [message.attachments && message.attachments.length > 0 ? (_jsx("div", { className: "jini-message-attachments", children: message.attachments.map((a) => (_jsx(SentAttachmentButton, { attachment: a, onOpen: gallery.open, renderAttachment: renderAttachment }, a.path))) })) : null, _jsx("div", { className: "jini-message-content", children: message.content }), _jsx("div", { className: "jini-message-actions jini-message-actions--user", children: _jsx(CopyMessageButton, { text: message.content, label: t('Copy message') }) }), gallery.opened ? (_jsx(AttachmentPreviewModal, { attachment: gallery.opened, onClose: gallery.close, navigation: gallery.navigation })) : null] }));
|
|
188
188
|
}
|
|
189
|
-
function AssistantMessageRow({ message, usageSummary, showUsageTotal, showUsageLine, toolCallDisplay, showThinking, runStreaming, runSending, runSucceeded, questionFormInteractive, questionFormSubmittedAnswers, onQuestionFormSubmit, projectFileNames, onRequestOpenFile, }) {
|
|
189
|
+
function AssistantMessageRow({ message, usageSummary, showUsageTotal, showUsageLine, toolCallDisplay, showThinking, runStreaming, runSending, sendingLabel, runSucceeded, questionFormInteractive, questionFormSubmittedAnswers, onQuestionFormSubmit, projectFileNames, onRequestOpenFile, }) {
|
|
190
190
|
const registry = useExtEventRendererRegistry();
|
|
191
191
|
const messageUsage = useMessageUsage({ message, ...(usageSummary === undefined ? {} : { summary: usageSummary }) });
|
|
192
192
|
const timeline = useToolTimeline({ events: message.events }, { runStreaming, runSucceeded });
|
|
@@ -206,7 +206,8 @@ function AssistantMessageRow({ message, usageSummary, showUsageTotal, showUsageL
|
|
|
206
206
|
});
|
|
207
207
|
const footerUsage = showUsageLine ? usage.footer : undefined;
|
|
208
208
|
const lastFinishedReply = !runInProgress && !usage.footer ? usage.replies.findLastIndex(value => value !== undefined) : -1;
|
|
209
|
-
const
|
|
209
|
+
const retriedRows = retriedToolRowIds({ rows: timeline.rows });
|
|
210
|
+
const renderToolCard = (row) => (_jsx(ToolCard, { retried: retriedRows.has(row.id), use: row.use, result: row.result, runStreaming: runStreaming, runSucceeded: runSucceeded, ...(projectFileNames !== undefined ? { projectFileNames } : {}), ...(onRequestOpenFile !== undefined ? { onRequestOpenFile } : {}) }, row.id));
|
|
210
211
|
// Hidden mode reuses the same disclosure and card renderer for unrecovered failures.
|
|
211
212
|
const renderTools = (rows, key) => (toolCallDisplay !== 'show'
|
|
212
213
|
? _jsx(ToolCallGroup, { rows: rows, renderRow: renderToolCard, failureOnly: toolCallDisplay === 'hidden' }, key)
|
|
@@ -279,7 +280,7 @@ function AssistantMessageRow({ message, usageSummary, showUsageTotal, showUsageL
|
|
|
279
280
|
? 'jini-message jini-message-assistant jini-message-assistant--usage-actions'
|
|
280
281
|
: 'jini-message jini-message-assistant', "data-message-id": message.id, "data-run-status": message.runStatus, "data-agent-element": `chat-message-${message.id}`, "data-agent-role": "region", "data-agent-label": "A reply from the assistant", children: [message.agentName ? _jsx("div", { className: "jini-message-agent", children: message.agentName }) : null, section
|
|
281
282
|
? section.map(renderBlock)
|
|
282
|
-
: (_jsxs(_Fragment, { children: [renderTextSegments(visibleContent, 'text'), flatTools.map((group) => renderTools(group.rows, group.key)), flatExtGroups.length > 0 ? _jsx("div", { className: "jini-message-ext-events", children: flatExtGroups.map(renderExtGroup) }) : null] })), footer && _jsx(AssistantMessageFooter, { message, usageEvent: footerUsage, showUsageTotal, terminalNotice, pending, runInProgress, runStreaming, runSending, visibleContent, isAnsweredCard: answeredCards.isAnsweredCard })] }, footer ? message.id : key));
|
|
283
|
+
: (_jsxs(_Fragment, { children: [renderTextSegments(visibleContent, 'text'), flatTools.map((group) => renderTools(group.rows, group.key)), flatExtGroups.length > 0 ? _jsx("div", { className: "jini-message-ext-events", children: flatExtGroups.map(renderExtGroup) }) : null] })), footer && _jsx(AssistantMessageFooter, { message, usageEvent: footerUsage, showUsageTotal, terminalNotice, pending, runInProgress, runStreaming, runSending, sendingLabel, visibleContent, isAnsweredCard: answeredCards.isAnsweredCard })] }, footer ? message.id : key));
|
|
283
284
|
// The lossless fallback keeps the host's text intact; user receipts still own separate rows,
|
|
284
285
|
// in their original receipt order, outside the assistant's actions and usage footer.
|
|
285
286
|
// Keep the same child-array nesting as the interleaved path so React preserves the footer.
|
|
@@ -318,13 +319,14 @@ function isUserSection({ section }, _options = {}) {
|
|
|
318
319
|
* The live "what is it doing" line under a running reply (see `core/run-activity.ts`). Replaces the
|
|
319
320
|
* old "Thinking…" placeholder, which vanished for good as soon as the first tool row appeared.
|
|
320
321
|
* Kept a separate component so its one-second tick re-renders only this line, not the whole row.
|
|
322
|
+
* A host's already-localized sending label is used verbatim until acceptance.
|
|
321
323
|
*/
|
|
322
|
-
function RunActivityLine({ events, active, isAnsweredCard, sending }) {
|
|
324
|
+
function RunActivityLine({ events, active, isAnsweredCard, sending, sendingLabel }) {
|
|
323
325
|
const t = useT();
|
|
324
326
|
const activityLabel = useRunActivity({ events: events, active: active }, { isAnsweredCard });
|
|
325
327
|
// Until the server accepts the start the turn is still on its way: "Thinking…" there read as the
|
|
326
328
|
// model working while the text had not even arrived (owner, 2026-10-27). No timeout.
|
|
327
|
-
const label = active && sending ? t('Sending…') : continuingRunNotice({ events, active }, {}) ?? activityLabel;
|
|
329
|
+
const label = active && sending ? sendingLabel ?? t('Sending…') : continuingRunNotice({ events, active }, {}) ?? activityLabel;
|
|
328
330
|
if (label === null)
|
|
329
331
|
return null;
|
|
330
332
|
return (_jsx("div", { className: "jini-message-pending jini-run-activity", "aria-live": "off", children: label }));
|
|
@@ -16,7 +16,9 @@ export interface ToolCardProps {
|
|
|
16
16
|
projectFileNames?: Set<string>;
|
|
17
17
|
/** Lifts a basename up to the host so it can focus the matching tab in its own file viewer. */
|
|
18
18
|
onRequestOpenFile?: (name: string) => void;
|
|
19
|
+
/** A later call of the same tool in this turn succeeded (`retriedToolRowIds`): a failure shows a neutral "Retried" badge, not red. */
|
|
20
|
+
retried?: boolean;
|
|
19
21
|
}
|
|
20
|
-
export declare function ToolCard({ use, result, runStreaming, runSucceeded, projectFileNames, onRequestOpenFile }: ToolCardProps): import("react").JSX.Element | null;
|
|
22
|
+
export declare function ToolCard({ use, result, runStreaming, runSucceeded, projectFileNames, onRequestOpenFile, retried }: ToolCardProps): import("react").JSX.Element | null;
|
|
21
23
|
export {};
|
|
22
24
|
//# sourceMappingURL=ToolCard.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
const RUNNING_TITLE_CLASS = ' shimmer-text';
|
|
3
3
|
/**
|
|
4
4
|
* @module ToolCard
|
|
@@ -21,18 +21,29 @@ const RUNNING_TITLE_CLASS = ' shimmer-text';
|
|
|
21
21
|
* `AskUserQuestion`) falls through to `GenericCard`, which is a correct,
|
|
22
22
|
* generic rendering for it.
|
|
23
23
|
*/
|
|
24
|
-
import { useId, useState } from 'react';
|
|
25
|
-
import { COLLAPSED_DETAIL_MAX_CHARS, describeCollapsedToolCall, describeToolCallDetail, isDelegatedWrapperToolName, isTodoWriteToolName, parseTodoWriteInput, redactUserText, toRenderProps } from '../../core/index.js';
|
|
24
|
+
import { createContext, useContext, useId, useMemo, useState } from 'react';
|
|
25
|
+
import { COLLAPSED_DETAIL_MAX_CHARS, describeCollapsedToolCall, describeToolCallDetail, humanizeToolName, isDelegatedWrapperToolName, isTodoWriteToolName, parseTodoWriteInput, redactUserText, toRenderProps } from '../../core/index.js';
|
|
26
26
|
import { useT, useToolRendererRegistry } from '../hooks/context.js';
|
|
27
27
|
import { Icon } from './Icon.js';
|
|
28
|
+
import { SAFE_LINK_HREF } from './Markdown.js';
|
|
28
29
|
import { TodoCard } from './TodoCard.js';
|
|
30
|
+
import { toolRowTarget } from './ToolCard.rules.js';
|
|
31
|
+
import { toolPresentation } from '../../core/tool-presentation.js';
|
|
32
|
+
/**
|
|
33
|
+
* Read by `ErrorBadge` only. A context, not one more prop threaded through every family card,
|
|
34
|
+
* because each card renders its own badge and all of them must agree.
|
|
35
|
+
*/
|
|
36
|
+
const RetriedContext = createContext(false);
|
|
29
37
|
/** A Jini registry tool id: dotted, lowercase, snake_case segments — `page.fill`, `daemon.db.vacuum`, `chat.send_message`. Never matches an agent-vendor tool name (`Bash`, `Write`, `mcp__jini__execute_delegated_tool`), which is the point — this is how a canonical delegated-tool event is told apart from everything else. */
|
|
30
38
|
const JINI_TOOL_ID_PATTERN = /^[a-z][a-z_]*(\.[a-z][a-z_]*)+$/;
|
|
31
|
-
export function ToolCard({ use, result, runStreaming, runSucceeded, projectFileNames, onRequestOpenFile }) {
|
|
39
|
+
export function ToolCard({ use, result, runStreaming, runSucceeded, projectFileNames, onRequestOpenFile, retried = false }) {
|
|
32
40
|
const registry = useToolRendererRegistry();
|
|
41
|
+
// Resolve vendor dialects for rendering only; tool ids and stored events stay intact.
|
|
42
|
+
const presentation = toolPresentation(use);
|
|
43
|
+
use = { ...use, ...presentation };
|
|
33
44
|
const name = use.name;
|
|
34
45
|
const card = { input: use.input, result: result, runStreaming: runStreaming ?? false, runSucceeded: runSucceeded ?? false };
|
|
35
|
-
const content = renderToolCardBody({ use, card, fileCtx: { projectFileNames, onRequestOpenFile }, registry });
|
|
46
|
+
const content = renderToolCardBody({ use, card, wrapper: presentation.wrapper, fileCtx: { projectFileNames, onRequestOpenFile }, registry });
|
|
36
47
|
if (content === null)
|
|
37
48
|
return null;
|
|
38
49
|
// Tagged once here, at the single dispatch point every branch below funnels through, rather than
|
|
@@ -40,7 +51,7 @@ export function ToolCard({ use, result, runStreaming, runSucceeded, projectFileN
|
|
|
40
51
|
// host's own custom-registered renderer) gets it automatically. `use.id` is this tool call's own
|
|
41
52
|
// stable identifier (unique per call within a message), never reused, so this handle is never
|
|
42
53
|
// ambiguous the way a repeated literal handle would be.
|
|
43
|
-
return (_jsx("div", { "data-agent-element": `tool-call-${use.id}`, "data-agent-role": "region", "data-agent-label": `A ${
|
|
54
|
+
return (_jsx("div", { "data-agent-element": `tool-call-${use.id}`, "data-agent-role": "region", "data-agent-label": `A ${humanizeToolName({ name: name })} tool call the AI made`, children: _jsx(RetriedContext.Provider, { value: retried, children: content }) }));
|
|
44
55
|
}
|
|
45
56
|
// Hardcoded family cards for tools shipped with this package, keyed by every name each tool goes
|
|
46
57
|
// by. A `Map`, not an object literal, so a tool named `constructor`/`toString` can never resolve
|
|
@@ -90,7 +101,8 @@ function renderToolCardBody(args) {
|
|
|
90
101
|
// `useToolTimeline`: `foldDelegatedWrapperCalls` drops it in favour of that canonical row. This
|
|
91
102
|
// check still covers a host that renders `<ToolCard>` straight from raw events.)
|
|
92
103
|
// Covers the read-only gateway (`execute_readonly_delegated_tool`) too.
|
|
93
|
-
|
|
104
|
+
// An outer transport receipt may have no bridge row; keep its inner call visible.
|
|
105
|
+
if (!args.wrapper && isDelegatedWrapperToolName({ name: name }) && card.result && !card.result.isError)
|
|
94
106
|
return null;
|
|
95
107
|
if (isTodoWriteToolName({ name: name }))
|
|
96
108
|
return _jsx(TodoCard, { todos: parseTodoWriteInput({ input: use.input }), runStreaming: card.runStreaming });
|
|
@@ -108,28 +120,29 @@ function renderToolCardBody(args) {
|
|
|
108
120
|
}
|
|
109
121
|
return _jsx(GenericCard, { name: name, ...card });
|
|
110
122
|
}
|
|
111
|
-
/**
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
123
|
+
/**
|
|
124
|
+
* The head every Jini/MCP/unknown tool row shares: status, "<Humanized Name>" (announced as
|
|
125
|
+
* "Tool call · <Humanized Name>"), and the
|
|
126
|
+
* target ({@link toolRowTarget}). One place, so a tool id the family table doesn't know (`content_post_update`,
|
|
127
|
+
* `mcp__tovu__theme_list_files`) still reads as a label and never as its raw name and JSON args.
|
|
128
|
+
*/
|
|
129
|
+
function ToolRowHead({ name, input, result, runStreaming, runSucceeded }) {
|
|
130
|
+
const t = useT();
|
|
131
|
+
// Streaming deltas recreate result envelopes; parse only when the target's inputs change.
|
|
132
|
+
// A failed event's payload describes the error even when it contains human-looking names.
|
|
133
|
+
const resultContent = result?.content;
|
|
134
|
+
const resultIsError = result?.isError;
|
|
135
|
+
const target = useMemo(() => toolRowTarget({ input: input, resultContent: resultIsError ? undefined : resultContent }), [input, resultContent, resultIsError]);
|
|
136
|
+
const summary = target ? headSummary({ text: target }) : undefined;
|
|
137
|
+
const prefix = `${t('Tool call')} · `;
|
|
138
|
+
const toolName = humanizeToolName({ name: name });
|
|
139
|
+
const isRunning = runStreaming && !result;
|
|
140
|
+
// Name and target each carry their full text as a tooltip: on a narrow dock the name truncates
|
|
141
|
+
// first and the target is capped (styles.ts `.op-title`/`.op-meta`), so either can be cut.
|
|
142
|
+
// The "Tool call · " prefix is visually hidden (owner bug 11-01: it took half a ~333px dock row, so
|
|
143
|
+
// names read "Tool call · Cont…") but stays in the row's accessible name. The name's tooltip must
|
|
144
|
+
// not repeat it: trees that read a child's title before its text announced "Tool call · Tool call ·".
|
|
145
|
+
return (_jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: "op-sr-prefix", children: prefix }), _jsx("span", { className: `op-title${isRunning ? RUNNING_TITLE_CLASS : ''}`, title: toolName, children: toolName }), summary ? _jsx("span", { className: "op-meta op-target", title: summary, children: summary }) : null] }));
|
|
133
146
|
}
|
|
134
147
|
/**
|
|
135
148
|
* A card for one `page.*`/`daemon.db.*`/`chat.*`/… call — the canonical event
|
|
@@ -139,15 +152,12 @@ function primaryTarget(input) {
|
|
|
139
152
|
* blob this replaces.
|
|
140
153
|
*/
|
|
141
154
|
function DelegatedToolCard({ name, input, result, runStreaming, runSucceeded }) {
|
|
142
|
-
const t = useT();
|
|
143
155
|
// Wrapper form (only reached on a failed wrapper call, per ToolCard's suppression comment):
|
|
144
156
|
// the real id and args are nested under {toolId, input}, not `input` itself.
|
|
145
157
|
const wrapper = (input ?? {});
|
|
146
158
|
const toolId = isDelegatedWrapperToolName({ name: name }) ? (wrapper.toolId ?? name) : name;
|
|
147
159
|
const args = isDelegatedWrapperToolName({ name: name }) ? wrapper.input : input;
|
|
148
|
-
|
|
149
|
-
const isRunning = runStreaming && !result;
|
|
150
|
-
return (_jsx(ExpandableCard, { className: "op-jini-tool", call: { input: args ?? {}, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsxs("span", { className: `op-title${isRunning ? RUNNING_TITLE_CLASS : ''}`, children: [t('Tool call'), " \u00B7 ", humanizeToolId(toolId)] }), target ? _jsx("span", { className: "op-meta", children: headSummary({ text: target }) }) : null] }), after: _jsx(FileErrorDetail, { result: result }), children: _jsx(ToolCallDetail, { input: args ?? {}, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded, children: _jsx(ToolResultMedia, { result: result }) }) }));
|
|
160
|
+
return (_jsx(ExpandableCard, { className: "op-jini-tool", call: { input: args ?? {}, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsx(ToolRowHead, { name: toolId, input: args, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), after: _jsx(FileErrorDetail, { result: result }), children: _jsx(ToolCallDetail, { input: args ?? {}, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded, children: _jsx(ToolResultMedia, { result: result }) }) }));
|
|
151
161
|
}
|
|
152
162
|
function SearchToolsCard({ input, result, runStreaming, runSucceeded }) {
|
|
153
163
|
const t = useT();
|
|
@@ -236,8 +246,7 @@ function WebSearchCard({ input, result, runStreaming, runSucceeded }) {
|
|
|
236
246
|
}
|
|
237
247
|
/** Any tool this package has no family card for — an MCP tool, or a card tool such as `assistant_ask_choice` whose form renders elsewhere in the turn. The row still opens to the full call. */
|
|
238
248
|
function GenericCard({ name, input, result, runStreaming, runSucceeded }) {
|
|
239
|
-
|
|
240
|
-
return (_jsx(ExpandableCard, { className: "op-generic", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: "op-title", children: name }), summary ? _jsx("span", { className: "op-meta", children: headSummary({ text: summary }) }) : null] }), after: _jsx(ToolResultMedia, { result: result }), children: _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
|
|
249
|
+
return (_jsx(ExpandableCard, { className: "op-generic", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsx(ToolRowHead, { name: name, input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), after: _jsx(ToolResultMedia, { result: result }), children: _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
|
|
241
250
|
}
|
|
242
251
|
/**
|
|
243
252
|
* The one row shell every card shares: a real `<button>` head (Enter/Space toggle it) that reports
|
|
@@ -255,7 +264,14 @@ function ExpandableCard({ className, head, after, call, children }) {
|
|
|
255
264
|
}
|
|
256
265
|
/** The collapsed row's stand-in for its panel — see {@link ExpandableCard} and `describeCollapsedToolCall`. */
|
|
257
266
|
function CollapsedReaderText({ call }) {
|
|
258
|
-
const
|
|
267
|
+
const input = call.input;
|
|
268
|
+
const resultContent = call.result?.content;
|
|
269
|
+
const resultIsError = call.result?.isError ?? false;
|
|
270
|
+
// Streaming deltas recreate envelopes; only changed reader text needs formatting/redaction.
|
|
271
|
+
// Run state explains a missing result, but cannot change the text of a recorded result.
|
|
272
|
+
const runStreaming = !call.result && call.runStreaming;
|
|
273
|
+
const runSucceeded = !call.result && call.runSucceeded;
|
|
274
|
+
const text = useMemo(() => describeCollapsedToolCall({ input, result: resultContent === undefined ? undefined : { content: resultContent, isError: resultIsError } }, { runStreaming, runSucceeded }), [input, resultContent, resultIsError, runStreaming, runSucceeded]);
|
|
259
275
|
return _jsx("span", { className: "op-card-reader-text", hidden: true, children: text });
|
|
260
276
|
}
|
|
261
277
|
/** The call's input, its result (or why there is none), and any extra `children` — see `describeToolCallDetail` for the redaction and formatting rules. */
|
|
@@ -266,6 +282,13 @@ function ToolCallDetail({ input, result, runStreaming, runSucceeded, children })
|
|
|
266
282
|
const detail = describeToolCallDetail({ input: input, result: result }, { runStreaming: runStreaming, runSucceeded: runSucceeded, maxChars: Infinity });
|
|
267
283
|
return (_jsxs("div", { className: "op-card-detail", children: [detail.inputText !== null ? _jsx("pre", { className: "op-command op-input", children: detail.inputText }) : null, detail.outputText !== null ? _jsx("pre", { className: "op-output", children: detail.outputText }) : null, detail.noteKey !== null ? _jsx("p", { className: "op-detail-note", children: t(detail.noteKey) }) : null, children] }));
|
|
268
284
|
}
|
|
285
|
+
/** A failed result: neutral "Retried" when a later call of the same tool succeeded, else red. */
|
|
286
|
+
function ErrorBadge({ result }) {
|
|
287
|
+
const t = useT();
|
|
288
|
+
if (useContext(RetriedContext))
|
|
289
|
+
return (_jsx("span", { className: "op-status op-status-retried", title: t('Retried'), children: _jsx(Icon, { name: "retry", size: 14 }) }));
|
|
290
|
+
return (_jsx("span", { className: "op-status op-status-error", title: describeToolCallDetail({ input: null, result }, { maxChars: COLLAPSED_DETAIL_MAX_CHARS }).outputText ?? t('Error'), children: _jsx(Icon, { name: "close", size: 14 }) }));
|
|
291
|
+
}
|
|
269
292
|
function ResultBadge({ result, runStreaming, runSucceeded }) {
|
|
270
293
|
const t = useT();
|
|
271
294
|
if (!result && runStreaming)
|
|
@@ -273,7 +296,7 @@ function ResultBadge({ result, runStreaming, runSucceeded }) {
|
|
|
273
296
|
if (!result && !runSucceeded)
|
|
274
297
|
return (_jsx("span", { className: "op-status op-status-error", title: t('Error'), children: _jsx(Icon, { name: "close", size: 14 }) }));
|
|
275
298
|
if (result?.isError)
|
|
276
|
-
return
|
|
299
|
+
return _jsx(ErrorBadge, { result: result });
|
|
277
300
|
return (_jsx("span", { className: "op-status op-status-ok", title: t('Done'), children: _jsx(Icon, { name: "check", size: 14 }) }));
|
|
278
301
|
}
|
|
279
302
|
/**
|
|
@@ -295,7 +318,23 @@ function ToolResultMedia({ result }) {
|
|
|
295
318
|
const images = blocks.filter((block) => block.type === 'image');
|
|
296
319
|
if (images.length === 0)
|
|
297
320
|
return null;
|
|
298
|
-
return (_jsx("div", { className: "op-media", children: images.map((block, index) =>
|
|
321
|
+
return (_jsx("div", { className: "op-media", children: images.map((block, index) => _jsx(ToolResultImageFigure, { block: block }, index)) }));
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* One tool image: captioned by its `title` (also the alt text) and linked to its `href` in a new
|
|
325
|
+
* tab, so a picture a tool just saved opens full size. The href is tool output, so it gets the same
|
|
326
|
+
* safe-target rule as chat links (`Markdown.tsx`); `javascript:`/`data:` stays an unlinked image.
|
|
327
|
+
*/
|
|
328
|
+
function ToolResultImageFigure({ block }) {
|
|
329
|
+
const img = (_jsx("img", { className: "op-media-image", src: `data:${block.mimeType};base64,${block.data}`, alt: block.title ?? '', style: { maxWidth: '100%', height: 'auto', display: 'block' } }));
|
|
330
|
+
const linked = block.href !== undefined && SAFE_LINK_HREF.test(block.href) && !block.href.startsWith('#')
|
|
331
|
+
? _jsx("a", { className: "op-media-link", href: block.href, target: "_blank", rel: "noopener noreferrer", children: img })
|
|
332
|
+
: img;
|
|
333
|
+
if (block.title === undefined)
|
|
334
|
+
return linked;
|
|
335
|
+
return (
|
|
336
|
+
// No host stylesheet styles op-media yet; reset the UA figure margin (40px sides) inline, like the img.
|
|
337
|
+
_jsxs("figure", { className: "op-media-figure", style: { margin: 0 }, children: [linked, _jsx("figcaption", { className: "op-media-caption", children: block.title })] }));
|
|
299
338
|
}
|
|
300
339
|
function FileErrorDetail({ result }) {
|
|
301
340
|
if (!result?.isError)
|
|
@@ -305,26 +344,6 @@ function FileErrorDetail({ result }) {
|
|
|
305
344
|
const detail = describeToolCallDetail({ input: null, result }, { maxChars: COLLAPSED_DETAIL_MAX_CHARS });
|
|
306
345
|
return detail.outputText === null ? null : _jsx("pre", { className: "op-output", children: detail.outputText });
|
|
307
346
|
}
|
|
308
|
-
function describeInput(input) {
|
|
309
|
-
if (input == null)
|
|
310
|
-
return '';
|
|
311
|
-
if (typeof input === 'string')
|
|
312
|
-
return input;
|
|
313
|
-
if (typeof input !== 'object')
|
|
314
|
-
return String(input);
|
|
315
|
-
const obj = input;
|
|
316
|
-
for (const key of ['file_path', 'path', 'pattern', 'url', 'query', 'name', 'command']) {
|
|
317
|
-
const v = obj[key];
|
|
318
|
-
if (typeof v === 'string')
|
|
319
|
-
return v;
|
|
320
|
-
}
|
|
321
|
-
try {
|
|
322
|
-
return JSON.stringify(obj);
|
|
323
|
-
}
|
|
324
|
-
catch {
|
|
325
|
-
return '';
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
347
|
/**
|
|
329
348
|
* A head's one-line summary of an arbitrary argument. Heads stay mounted for every collapsed row,
|
|
330
349
|
* and the fallback argument can be a 20k-char payload or a key, so it gets the reader text's
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file What a generic tool row names as its target — pure, so `ToolCard.tsx`'s `ToolRowHead` only
|
|
3
|
+
* draws it. Moved out of `ToolCard.tsx` (11-01) when the target learned to read the result too.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* What a tool row names as its target, for a person, best first: an argument that names it (see
|
|
7
|
+
* {@link SUMMARY_KEYS}: handle, title, slug, path, query…), the name the result returned, a shallow
|
|
8
|
+
* batch summary (after the result name, so an incidental array argument like `tags` never hides the
|
|
9
|
+
* record a single-record call returned), the record's kind ("post"), then any other readable string argument. Never the JSON of the whole
|
|
10
|
+
* object — that read as raw machine output in the head (`{"id":"30127d…","kind":"post",…`); the
|
|
11
|
+
* full arguments are in the expanded detail — and never an opaque id (owner bug 11-01: "Content
|
|
12
|
+
* Post Update 30127d31-…"). `undefined` when nothing reads as a target.
|
|
13
|
+
*
|
|
14
|
+
* @complexity O(k) in the argument and result key counts; one `JSON.parse` of the result.
|
|
15
|
+
*/
|
|
16
|
+
export declare function toolRowTarget({ input, resultContent }: {
|
|
17
|
+
input: unknown;
|
|
18
|
+
resultContent?: string | undefined;
|
|
19
|
+
}): string | undefined;
|
|
20
|
+
//# sourceMappingURL=ToolCard.rules.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file What a generic tool row names as its target — pure, so `ToolCard.tsx`'s `ToolRowHead` only
|
|
3
|
+
* draws it. Moved out of `ToolCard.tsx` (11-01) when the target learned to read the result too.
|
|
4
|
+
*/
|
|
5
|
+
/** Argument keys that name *what* a call acted on, best first: an element handle or page, then a human title/name/slug, then a path or query, then an id. */
|
|
6
|
+
const SUMMARY_KEYS = ['element', 'page', 'handle', 'title', 'name', 'slug', 'file_path', 'path', 'pattern', 'url', 'query', 'command', 'toolId', 'id'];
|
|
7
|
+
/** Result fields that name a record for a person — what a write or read returns about its target. */
|
|
8
|
+
const RESULT_NAME_KEYS = ['title', 'name', 'slug'];
|
|
9
|
+
/** Argument keys that say what *kind* of thing a call acted on — the last-resort target. */
|
|
10
|
+
const KIND_KEYS = ['kind', 'entityType', 'type', 'collection'];
|
|
11
|
+
/** A UUID or a long bare hex id: meaningless to a person, so never shown as a row's target. */
|
|
12
|
+
const OPAQUE_ID = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[0-9a-f]{16,})$/i;
|
|
13
|
+
function readable(value) {
|
|
14
|
+
return typeof value === 'string' && value !== '' && !OPAQUE_ID.test(value);
|
|
15
|
+
}
|
|
16
|
+
function firstReadable(obj, keys) {
|
|
17
|
+
for (const key of keys) {
|
|
18
|
+
const value = obj[key];
|
|
19
|
+
if (readable(value))
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
/** Failure metadata describes the error, never the acted-on record. @complexity O(1). */
|
|
25
|
+
function isErrorResult(obj) {
|
|
26
|
+
const hasError = obj.error != null && obj.error !== false;
|
|
27
|
+
const failedStatus = obj.status === 'failed' || obj.status === 'error';
|
|
28
|
+
return hasError || failedStatus || obj.isError === true || obj.success === false;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The record name a JSON tool result carries — at its top level or one object level down
|
|
32
|
+
* (`{ post: { title } }`). `undefined` for failures, non-JSON text or a result with no name field.
|
|
33
|
+
*
|
|
34
|
+
* @complexity O(n + k) for content length n and top-level key count k; one `JSON.parse`.
|
|
35
|
+
*/
|
|
36
|
+
function resultName(content) {
|
|
37
|
+
if (!content || !content.trimStart().startsWith('{'))
|
|
38
|
+
return undefined;
|
|
39
|
+
let parsed;
|
|
40
|
+
try {
|
|
41
|
+
parsed = JSON.parse(content);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
if (parsed == null || typeof parsed !== 'object')
|
|
47
|
+
return undefined;
|
|
48
|
+
const obj = parsed;
|
|
49
|
+
if (isErrorResult(obj))
|
|
50
|
+
return undefined;
|
|
51
|
+
const nested = Object.values(obj).filter((v) => v != null && typeof v === 'object' && !Array.isArray(v));
|
|
52
|
+
return firstReadable(obj, RESULT_NAME_KEYS) ?? nested.map((v) => firstReadable(v, RESULT_NAME_KEYS)).find(Boolean);
|
|
53
|
+
}
|
|
54
|
+
/** A batch member's own target: a readable string member, or an object member's summary key. O(k). */
|
|
55
|
+
function memberTarget(first) {
|
|
56
|
+
if (readable(first))
|
|
57
|
+
return first;
|
|
58
|
+
return first != null && typeof first === 'object' ? firstReadable(first, SUMMARY_KEYS) : undefined;
|
|
59
|
+
}
|
|
60
|
+
/** A shallow batch target, independent of CLI and collection key. No document-body traversal.
|
|
61
|
+
* O(k) in the envelope and first member's key counts; never scans or serializes all members.
|
|
62
|
+
*/
|
|
63
|
+
function batchTarget(input) {
|
|
64
|
+
const members = Array.isArray(input) ? input : Object.values(input).find(Array.isArray);
|
|
65
|
+
if (!members?.length)
|
|
66
|
+
return undefined;
|
|
67
|
+
const target = memberTarget(members[0]);
|
|
68
|
+
if (!target)
|
|
69
|
+
return `${members.length} ${members.length === 1 ? 'item' : 'items'}`;
|
|
70
|
+
return members.length === 1 ? target : `${target} +${members.length - 1}`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* What a tool row names as its target, for a person, best first: an argument that names it (see
|
|
74
|
+
* {@link SUMMARY_KEYS}: handle, title, slug, path, query…), the name the result returned, a shallow
|
|
75
|
+
* batch summary (after the result name, so an incidental array argument like `tags` never hides the
|
|
76
|
+
* record a single-record call returned), the record's kind ("post"), then any other readable string argument. Never the JSON of the whole
|
|
77
|
+
* object — that read as raw machine output in the head (`{"id":"30127d…","kind":"post",…`); the
|
|
78
|
+
* full arguments are in the expanded detail — and never an opaque id (owner bug 11-01: "Content
|
|
79
|
+
* Post Update 30127d31-…"). `undefined` when nothing reads as a target.
|
|
80
|
+
*
|
|
81
|
+
* @complexity O(k) in the argument and result key counts; one `JSON.parse` of the result.
|
|
82
|
+
*/
|
|
83
|
+
export function toolRowTarget({ input, resultContent }) {
|
|
84
|
+
if (typeof input === 'string')
|
|
85
|
+
return readable(input) ? input : undefined;
|
|
86
|
+
if (input != null && typeof input !== 'object')
|
|
87
|
+
return String(input);
|
|
88
|
+
const args = (input ?? {});
|
|
89
|
+
const named = firstReadable(args, SUMMARY_KEYS) ?? resultName(resultContent);
|
|
90
|
+
return named ?? batchTarget(args) ?? firstReadable(args, KIND_KEYS) ?? Object.values(args).find(readable);
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=ToolCard.rules.js.map
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { pickerModelValue, pickerCatalogNote, pickerModelOptions, shortModelLabel } from '../model-options.js';
|
|
3
|
+
import { useId } from 'react';
|
|
3
4
|
import { createPortal } from 'react-dom';
|
|
4
5
|
import { AgentIcon, RemixIcon, SearchableModelSelect } from '@jini-ai/ui';
|
|
6
|
+
import { InfoTip } from '@jini-ai/ui/admin-widgets';
|
|
5
7
|
import { assessNativeToolPolicy } from '@jini-ai/agent-runtime/native-tool-policy';
|
|
6
8
|
import { useT } from '../../../hooks/context.js';
|
|
7
9
|
import { definedProps } from '../../../util/defined-props.js';
|
|
@@ -18,27 +20,37 @@ export function runtimeAgentStatus({ agent }) {
|
|
|
18
20
|
return 'Installed · sign-in required';
|
|
19
21
|
return agent.version ?? 'Installed';
|
|
20
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* An agent's `fullCliAccessNote` as a warning icon whose tip (hover, focus, tap) holds the text.
|
|
25
|
+
* It was a paragraph beside the model picker that squeezed the composer row (owner 2026-10-30).
|
|
26
|
+
* `descriptionId` lets the agent's own radio in the picker list read the same text.
|
|
27
|
+
*/
|
|
28
|
+
function FullAccessNoteTip({ note, descriptionId, t }) {
|
|
29
|
+
return (_jsx("span", { className: "jini-runtime-access-note", children: _jsx(InfoTip, { label: t(note), triggerLabel: t('Full computer access'), descriptionId: descriptionId, placement: "top", icon: _jsx(RemixIcon, { name: "alert-line", size: 16 }) }) }));
|
|
30
|
+
}
|
|
21
31
|
/** Selection preflight uses the launch assessor; it never substitutes for enforcement. */
|
|
22
32
|
function NativeToolPolicyWarning({ agent, policy, isApi }) {
|
|
23
33
|
const t = useT();
|
|
24
34
|
if (!agent || isApi)
|
|
25
35
|
return null;
|
|
26
36
|
const warning = policy ? assessNativeToolPolicy({ def: agent, policy }) : null;
|
|
27
|
-
return _jsxs(_Fragment, { children: [agent.fullCliAccessNote ? _jsx(
|
|
37
|
+
return _jsxs(_Fragment, { children: [agent.fullCliAccessNote ? _jsx(FullAccessNoteTip, { note: agent.fullCliAccessNote, t: t }) : null, warning ? _jsx("p", { role: "alert", children: t(warning) }) : null] });
|
|
28
38
|
}
|
|
29
39
|
function RuntimeModeButtons({ executionMode, daemonOnline, apiModeAvailable, onExecutionModeChange, t, }) {
|
|
30
40
|
const localModeLabel = executionMode === 'local' ? t('active') : '';
|
|
31
41
|
return (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: `jini-runtime-mode${executionMode === 'local' ? ' is-active' : ''}`, disabled: !daemonOnline && executionMode !== 'local', onClick: () => onExecutionModeChange?.('local'), children: [_jsx(RemixIcon, { name: "terminal-box-line", size: 18 }), _jsx("span", { children: t('Use Local CLI') }), _jsx("span", { className: "jini-runtime-mode__meta", children: !daemonOnline ? t('offline') : localModeLabel }), executionMode === 'local' ? (_jsx(RemixIcon, { name: "check-line", size: 19, className: "jini-runtime-check" })) : null] }), _jsxs("button", { type: "button", className: `jini-runtime-mode${executionMode === 'api' ? ' is-active' : ''}`, disabled: !apiModeAvailable, onClick: () => onExecutionModeChange?.('api'), children: [_jsx(RemixIcon, { name: "link", size: 18 }), _jsx("span", { children: t('Use API · BYOK') }), !apiModeAvailable ? _jsx("span", { className: "jini-runtime-mode__meta", children: t('not configured') }) : null, executionMode === 'api' ? (_jsx(RemixIcon, { name: "check-line", size: 19, className: "jini-runtime-check" })) : null] })] }));
|
|
32
42
|
}
|
|
33
43
|
function RuntimeAgentList({ orderedAgents, value, onChange, agentIconBasePath, t, }) {
|
|
44
|
+
const noteIdPrefix = useId();
|
|
34
45
|
if (orderedAgents.length === 0) {
|
|
35
46
|
return _jsx("div", { className: "jini-runtime-empty", children: t('No available code agents') });
|
|
36
47
|
}
|
|
37
|
-
return (_jsx(_Fragment, { children: orderedAgents.map((agent) => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
48
|
+
return (_jsx(_Fragment, { children: orderedAgents.map((agent) => (_jsx(RuntimeAgentRow, { agent: agent, active: agent.id === value.agentId, noteId: agent.fullCliAccessNote ? `${noteIdPrefix}-${agent.id}-note` : undefined, onChange: onChange, agentIconBasePath: agentIconBasePath, t: t }, agent.id))) }));
|
|
49
|
+
}
|
|
50
|
+
function RuntimeAgentRow({ agent, active, noteId, onChange, agentIconBasePath, t }) {
|
|
51
|
+
const status = runtimeAgentStatus({ agent: agent });
|
|
52
|
+
// The note's icon is a control of its own, so it sits beside the radio, never inside it.
|
|
53
|
+
return (_jsxs("div", { className: "jini-runtime-agent-row", children: [_jsxs("button", { type: "button", role: "radio", "aria-checked": active, className: `jini-runtime-agent${active ? ' is-active' : ''}`, "aria-current": active ? 'true' : undefined, "aria-label": `${agent.name} · ${status}`, ...(noteId ? { 'aria-describedby': noteId } : {}), onClick: () => onChange(defaultChatPaneSelection({ agent: agent })), children: [_jsx(AgentIcon, { id: agent.id, size: 20, className: "jini-runtime-agent-icon", ...definedProps({ source: { basePath: agentIconBasePath } }) }), _jsx("span", { className: "jini-runtime-agent__copy", children: _jsx("strong", { children: agent.name }) }), agent.supportsTools === false ? (_jsx("span", { className: "jini-runtime-agent__badge", title: t("This CLI has no MCP support, so it cannot use this app's tools."), children: t('No tools') })) : null, _jsx("span", { className: "jini-runtime-agent__status", children: active ? t('selected') : status }), active ? (_jsx(RemixIcon, { name: "check-line", size: 19, className: "jini-runtime-check" })) : null] }), agent.fullCliAccessNote ? _jsx(FullAccessNoteTip, { note: agent.fullCliAccessNote, descriptionId: noteId, t: t }) : null] }));
|
|
42
54
|
}
|
|
43
55
|
/**
|
|
44
56
|
* The composer bar's "Model ⌄" and "Effort ⌄" drop-ups for the selected CLI. These replace the
|
|
@@ -302,11 +302,11 @@ function buildChatPaneSlots({ surfaceSlots, embed, renderRuntimePicker }, { lead
|
|
|
302
302
|
footerAccessories: embed ? undefined : renderRuntimePicker(),
|
|
303
303
|
} });
|
|
304
304
|
}
|
|
305
|
-
function ChatPaneTranscript({ pane, emptyState, embed, suggestions, onSelectSuggestion, pendingPrompts, projectFileNameSet, toolCallDisplay, showUsageLine, showThinking, t, }) {
|
|
305
|
+
function ChatPaneTranscript({ pane, sendingLabel, emptyState, embed, suggestions, onSelectSuggestion, pendingPrompts, projectFileNameSet, toolCallDisplay, showUsageLine, showThinking, t, }) {
|
|
306
306
|
if (pane.conversation.messages.length === 0 && emptyState) {
|
|
307
307
|
return (_jsxs("div", { className: "jini-message-list jini-message-list--empty", children: [emptyState, embed && _jsx(ChatPaneSuggestionsRow, { suggestions: suggestions, onSelect: onSelectSuggestion, t: t })] }));
|
|
308
308
|
}
|
|
309
|
-
return (_jsx(MessageList, { messages: pane.conversation.messages, isStreaming: pane.conversation.isStreaming, scrollIntent: pane.conversation.scrollIntent, onScrolled: pane.conversation.acknowledgeScroll, pendingPrompts: pendingPrompts, toolCallDisplay: toolCallDisplay, showUsageLine: showUsageLine, showThinking: showThinking, ...(projectFileNameSet === undefined ? {} : { projectFileNames: projectFileNameSet }) }));
|
|
309
|
+
return (_jsx(MessageList, { messages: pane.conversation.messages, ...definedProps({ source: { sendingLabel } }), isStreaming: pane.conversation.isStreaming, scrollIntent: pane.conversation.scrollIntent, onScrolled: pane.conversation.acknowledgeScroll, pendingPrompts: pendingPrompts, toolCallDisplay: toolCallDisplay, showUsageLine: showUsageLine, showThinking: showThinking, ...(projectFileNameSet === undefined ? {} : { projectFileNames: projectFileNameSet }) }));
|
|
310
310
|
}
|
|
311
311
|
/** The controls-area suggestions row shows on every workspace surface, and on the embed surface
|
|
312
312
|
* only when the empty transcript has no `emptyState` to host it instead. */
|
|
@@ -404,6 +404,6 @@ export function ChatPane(props) {
|
|
|
404
404
|
const selectSuggestion = useChatPaneSuggestion({ rootRef, setDraft: pane.composer.setDraft });
|
|
405
405
|
const openLinkInNewTab = useChatPaneNewTabLinks();
|
|
406
406
|
const { pendingPrompts, projectFileNameSet } = useChatPaneTranscriptProps({ pane }, definedProps({ source: { projectFileNames: p.projectFileNames } }));
|
|
407
|
-
return (_jsxs("section", { className: chatPaneClassName(p.variant, p.className), style: p.style, "data-activity": pane.activity, ref: rootRef, onClickCapture: openLinkInNewTab, children: [_jsx("style", { "data-jini-chat-pane-styles": "true", children: CHAT_PANE_STYLES }), p.header ?? defaultHeader(p.title, pane.reset, t), p.agentPluginsMode !== 'off' && _jsx(AgentPluginUsage, { conversationId: pane.conversation.conversationId, messages: pane.conversation.messages }), _jsxs("div", { className: "jini-chat-pane__body", children: [_jsx(ChatPaneTranscript, { pane: pane, emptyState: p.emptyState, embed: surface.embed, suggestions: p.suggestions, onSelectSuggestion: selectSuggestion, pendingPrompts: pendingPrompts, projectFileNameSet: projectFileNameSet, toolCallDisplay: p.toolCallDisplay, showUsageLine: p.showUsageLine, showThinking: p.showThinking, t: t }), _jsxs("div", { className: "jini-chat-pane__controls", ref: controlsRef, children: [showsControlsSuggestions(surface.embed, pane.conversation.messages.length, p.emptyState) && (_jsx(ChatPaneSuggestionsRow, { suggestions: p.suggestions, onSelect: selectSuggestion, t: t })), pane.secretRedacted ?
|
|
407
|
+
return (_jsxs("section", { className: chatPaneClassName(p.variant, p.className), style: p.style, "data-activity": pane.activity, ref: rootRef, onClickCapture: openLinkInNewTab, children: [_jsx("style", { "data-jini-chat-pane-styles": "true", children: CHAT_PANE_STYLES }), p.header ?? defaultHeader(p.title, pane.reset, t), p.agentPluginsMode !== 'off' && _jsx(AgentPluginUsage, { conversationId: pane.conversation.conversationId, messages: pane.conversation.messages }), _jsxs("div", { className: "jini-chat-pane__body", children: [_jsx(ChatPaneTranscript, { pane: pane, sendingLabel: p.sendingLabel, emptyState: p.emptyState, embed: surface.embed, suggestions: p.suggestions, onSelectSuggestion: selectSuggestion, pendingPrompts: pendingPrompts, projectFileNameSet: projectFileNameSet, toolCallDisplay: p.toolCallDisplay, showUsageLine: p.showUsageLine, showThinking: p.showThinking, t: t }), _jsxs("div", { className: "jini-chat-pane__controls", ref: controlsRef, children: [showsControlsSuggestions(surface.embed, pane.conversation.messages.length, p.emptyState) && (_jsx(ChatPaneSuggestionsRow, { suggestions: p.suggestions, onSelect: selectSuggestion, t: t })), pane.secretRedacted ? (_jsxs("div", { className: "jini-chat-pane__status jini-chat-pane__status--dismissible", role: "status", children: [_jsx("span", { children: t(SECRET_REDACTED_NOTICE) }), _jsx("button", { type: "button", className: "jini-chat-pane__error-action", onClick: pane.dismissSecretRedacted, children: t('Dismiss') })] })) : null, _jsx(ChatPaneStatusMessages, { unavailable: unavailable, scanningAgents: runtimeView.scanningAgents, connectingAgents: runtimeView.connectingAgents, executionMode: p.executionMode, apiModeAvailable: p.apiModeAvailable, onExecutionModeChange: p.onExecutionModeChange, sendError: pane.conversation.sendError, onRetrySend: () => void pane.retrySend(), attachmentError: pane.attachmentError, dropReadError: fileDrop.dropReadError, workingDirectoryError: pane.workingDirectoryError, workingDirectoryPending: pane.workingDirectoryPending, workingDirectoryInvalid: pane.workingDirectoryInvalid, runtimeInventoryError: inventory.runtimeInventoryError, typedAnswerNotice: pane.typedAnswerNotice, t: t }), _jsx(ChatPaneComposerArea, { fileDrop: fileDrop, uploadAttachments: surface.uploadAttachments, pane: pane, disabled: p.disabled, unavailable: unavailable, placeholder: composerPlaceholder, slots: slots, attachmentAccept: p.attachmentAccept, workingDirectoryAccess: p.workingDirectoryAccess, workingDirectoryControlPlacement: surface.workingDirectoryControlPlacement, t: t })] })] }), p.footer] }));
|
|
408
408
|
}
|
|
409
409
|
//# sourceMappingURL=ChatPane.js.map
|
|
@@ -44,8 +44,10 @@ export interface UseChatPaneOptions extends UserTextRedactionOptions {
|
|
|
44
44
|
deliverMidRunMessage?: DeliverMidRunMessage;
|
|
45
45
|
}
|
|
46
46
|
export interface UseChatPaneResult extends UseChatPaneWorkingDirectoryResult {
|
|
47
|
-
/** Show a localized non-secret notice when a paste was removed. */
|
|
47
|
+
/** Show a localized non-secret notice when a paste was removed. Cleared by the next send or {@link dismissSecretRedacted}. */
|
|
48
48
|
secretRedacted: boolean;
|
|
49
|
+
/** Hides the redaction notice. */
|
|
50
|
+
dismissSecretRedacted: () => void;
|
|
49
51
|
conversation: UseConversationResult;
|
|
50
52
|
composer: UseComposerResult;
|
|
51
53
|
selection: ChatPaneAgentSelection;
|
|
@@ -680,6 +680,7 @@ export function useChatPane(options) {
|
|
|
680
680
|
if (!prompt || composerDeliveryBlocked({ delivering: deliveringTypedAnswerRef.current, starting: startingPromptRef.current, composer, submitted: submittedComposerRef.current }))
|
|
681
681
|
return;
|
|
682
682
|
setTypedAnswerNotice(null);
|
|
683
|
+
guard.clearNotice();
|
|
683
684
|
// Until upload completes, an empty attachment list is not evidence of a text-only answer.
|
|
684
685
|
if (uploadingWhileStreaming) {
|
|
685
686
|
await sendOrdinaryTurn(prompt);
|
|
@@ -701,6 +702,7 @@ export function useChatPane(options) {
|
|
|
701
702
|
composer,
|
|
702
703
|
conversation.isStreaming,
|
|
703
704
|
conversation.messages,
|
|
705
|
+
guard.clearNotice,
|
|
704
706
|
options.deliverTypedAnswer,
|
|
705
707
|
sendBlocker,
|
|
706
708
|
sendOrdinaryTurn,
|
|
@@ -720,16 +722,18 @@ export function useChatPane(options) {
|
|
|
720
722
|
if (!prompt || composerDeliveryBlocked({ delivering: deliveringTypedAnswerRef.current, starting: startingPromptRef.current, composer, submitted: submittedComposerRef.current }))
|
|
721
723
|
return;
|
|
722
724
|
setTypedAnswerNotice(null);
|
|
725
|
+
guard.clearNotice();
|
|
723
726
|
await sendOrdinaryTurn(prompt);
|
|
724
|
-
}, [composer, sendOrdinaryTurn]);
|
|
727
|
+
}, [composer, guard.clearNotice, sendOrdinaryTurn]);
|
|
725
728
|
const interruptSend = useCallback(() => {
|
|
726
729
|
const prompt = composerPrompt(composer);
|
|
727
730
|
if (!prompt || composerDeliveryBlocked({ delivering: deliveringTypedAnswerRef.current, starting: startingPromptRef.current, composer, submitted: submittedComposerRef.current }))
|
|
728
731
|
return;
|
|
732
|
+
guard.clearNotice();
|
|
729
733
|
enqueueTurn({ turn: guard.redact({ text: prompt }, {}) });
|
|
730
734
|
composer.setDraft('');
|
|
731
735
|
conversation.cancel();
|
|
732
|
-
}, [composer, conversation, enqueueTurn, guard.redact]);
|
|
736
|
+
}, [composer, conversation, enqueueTurn, guard.clearNotice, guard.redact]);
|
|
733
737
|
/** Abort only uploads owned by the removed inputs; late completions must not refill the composer. */
|
|
734
738
|
const discardQueuedUploads = useCallback(({ id }, _options = {}) => {
|
|
735
739
|
for (const [controller, owner] of queuedUploadOwnersRef.current) {
|
|
@@ -799,6 +803,7 @@ export function useChatPane(options) {
|
|
|
799
803
|
composer.reset();
|
|
800
804
|
setAttachmentError(null);
|
|
801
805
|
setTypedAnswerNotice(null);
|
|
806
|
+
guard.clearNotice();
|
|
802
807
|
typedAnswerGenerationRef.current += 1;
|
|
803
808
|
// Without this, a prompt queued behind a streaming run survives `reset()` and — once
|
|
804
809
|
// `conversation.cancel()` above clears the streaming blocker — the flush effect fires it into
|
|
@@ -808,9 +813,10 @@ export function useChatPane(options) {
|
|
|
808
813
|
queuedUploadOwnersRef.current.clear();
|
|
809
814
|
setQueuedInputs([]);
|
|
810
815
|
refusedSendRef.current = null;
|
|
811
|
-
}, [composer, conversation, options.initialMessages]);
|
|
816
|
+
}, [composer, conversation, guard.clearNotice, options.initialMessages]);
|
|
812
817
|
return {
|
|
813
818
|
secretRedacted: guard.secretRedacted,
|
|
819
|
+
dismissSecretRedacted: guard.clearNotice,
|
|
814
820
|
conversation,
|
|
815
821
|
composer,
|
|
816
822
|
selection,
|