@jini-ai/chat 0.6.15-rc.2 → 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/accepted-input.js +20 -11
- package/dist/core/ag-ui/entry.js +3 -1
- package/dist/core/agent-plugin-chat-state.js +3 -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 +14 -2
- package/dist/core/assistant-client/attachment-liveness.js +1 -1
- package/dist/core/assistant-client/durable-run-subscription.js +2 -1
- package/dist/core/assistant-client/transport.js +13 -7
- package/dist/core/assistant-content.js +2 -1
- package/dist/core/composer-history.js +2 -1
- package/dist/core/durable-projection.js +2 -1
- package/dist/core/durable-runs/continuation.js +2 -1
- package/dist/core/events.d.ts +5 -1
- package/dist/core/messages.d.ts +5 -1
- package/dist/core/partial-json.js +18 -9
- package/dist/core/persistence/title.d.ts +1 -1
- package/dist/core/persistence/title.js +25 -7
- package/dist/core/question-form/normalize.js +1 -1
- package/dist/core/question-form/scan.js +25 -15
- package/dist/core/question-form/secret-answers.js +2 -1
- package/dist/core/run-activity.js +8 -4
- package/dist/core/run-events/entry.js +6 -2
- 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/transcript.js +13 -6
- package/dist/core/turn-usage.js +7 -2
- package/dist/core/user-text-redaction.d.ts +4 -6
- package/dist/core/user-text-redaction.js +13 -6
- package/dist/core/util/manifest.js +13 -11
- package/dist/core/util/markdown-context.d.ts +14 -0
- package/dist/core/util/markdown-context.js +42 -4
- package/dist/core/util/markup-attributes.js +1 -1
- package/dist/core/util/parser.js +7 -7
- package/dist/core/util/pointer.js +22 -6
- package/dist/core/util/recover.js +5 -8
- package/dist/core/util/strip.js +12 -15
- package/dist/core/util/validate.js +30 -18
- 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/lazy-loader.js +16 -12
- 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/dialog.hooks.js +2 -2
- package/dist/embed-widget/react/embed.d.ts +3 -10
- package/dist/embed-widget/react/embed.js +8 -52
- package/dist/embed-widget/react/header.hooks.js +1 -1
- package/dist/embed-widget/transports/fetch-sse.js +2 -1
- package/dist/embed-widget/vanilla/index.d.ts +0 -5
- package/dist/embed-widget/vanilla/index.js +23 -26
- package/dist/embed-widget/vanilla/state.d.ts +3 -11
- package/dist/embed-widget/vanilla/state.js +10 -39
- 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/AttachmentPreviewModal.hooks.js +2 -1
- package/dist/react/components/ChatSettingsDialog.js +18 -9
- package/dist/react/components/Composer.js +5 -4
- package/dist/react/components/ConversationList.js +5 -4
- 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 +30 -27
- package/dist/react/components/McpUiSurfaceCard.js +2 -1
- package/dist/react/components/MessageList.d.ts +1 -1
- package/dist/react/components/MessageList.hooks.js +3 -2
- package/dist/react/components/MessageRow.d.ts +3 -0
- package/dist/react/components/MessageRow.js +32 -14
- package/dist/react/components/MessageRow.usage.js +3 -1
- package/dist/react/components/QuestionForm.js +4 -5
- package/dist/react/components/SentAttachment.js +4 -1
- package/dist/react/components/TodoCard.js +9 -1
- package/dist/react/components/ToolCard.d.ts +3 -1
- package/dist/react/components/ToolCard.js +84 -64
- package/dist/react/components/ToolCard.rules.d.ts +20 -0
- package/dist/react/components/ToolCard.rules.js +92 -0
- package/dist/react/components/composer-discovery.js +8 -8
- package/dist/react/features/chat-pane/components/AgentPluginUsage.js +1 -2
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.d.ts +1 -1
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +31 -9
- package/dist/react/features/chat-pane/components/ChatPane.js +7 -5
- package/dist/react/features/chat-pane/components/RuntimeDropUp.js +1 -1
- package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +1 -1
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +2 -2
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +3 -1
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +13 -5
- package/dist/react/features/chat-pane/hooks/useChatPaneAgentControl.hooks.js +2 -2
- package/dist/react/features/chat-pane/hooks/useChatPaneControlsHeight.hooks.js +1 -1
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +2 -2
- package/dist/react/features/chat-pane/hooks/useChatPaneWorkingDirectory.hooks.js +2 -2
- package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.js +2 -2
- package/dist/react/features/chat-pane/model-options.js +4 -2
- package/dist/react/features/chat-pane/styles.d.ts +1 -1
- package/dist/react/features/chat-pane/styles.js +95 -5
- package/dist/react/features/chat-pane/types.d.ts +11 -0
- package/dist/react/hooks/assistant-chats.js +22 -8
- package/dist/react/hooks/useArtifactStream.js +2 -1
- package/dist/react/hooks/useComposer.js +2 -2
- package/dist/react/hooks/useComposerHistoryKeys.hooks.js +2 -2
- package/dist/react/hooks/useConversation.js +9 -4
- package/dist/react/hooks/useExtEventGroups.js +4 -2
- package/dist/react/hooks/useLatestOperation.js +2 -1
- package/dist/react/hooks/useQuestionForms.js +32 -22
- package/dist/react/hooks/useRunStream.js +4 -2
- package/dist/react/hooks/useUserTextGuard.d.ts +1 -0
- package/dist/react/hooks/useUserTextGuard.js +3 -1
- package/dist/react/message-blocks.js +4 -2
- package/dist/react/styles/reference.css +9 -0
- package/dist/react/tool-call-display.d.ts +11 -0
- package/dist/react/tool-call-display.js +59 -2
- package/dist/server/durable-runs/recover.js +13 -5
- package/dist/server/run-finalizer/entry.js +1 -0
- package/dist/store/runtime/chat-history-store.js +9 -3
- package/dist/store/runtime/durable-run-store.js +34 -24
- package/dist/store/runtime/run-ledger.js +7 -4
- package/dist/store/sql/boundary.js +3 -1
- package/dist/store/sql/cursor.js +8 -3
- package/dist/store/sql/store.js +36 -28
- package/package.json +93 -88
- 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
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
const RUNNING_TITLE_CLASS = ' shimmer-text';
|
|
2
3
|
/**
|
|
3
4
|
* @module ToolCard
|
|
4
5
|
*
|
|
@@ -20,18 +21,29 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
20
21
|
* `AskUserQuestion`) falls through to `GenericCard`, which is a correct,
|
|
21
22
|
* generic rendering for it.
|
|
22
23
|
*/
|
|
23
|
-
import { useId, useState } from 'react';
|
|
24
|
-
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';
|
|
25
26
|
import { useT, useToolRendererRegistry } from '../hooks/context.js';
|
|
26
27
|
import { Icon } from './Icon.js';
|
|
28
|
+
import { SAFE_LINK_HREF } from './Markdown.js';
|
|
27
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);
|
|
28
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. */
|
|
29
38
|
const JINI_TOOL_ID_PATTERN = /^[a-z][a-z_]*(\.[a-z][a-z_]*)+$/;
|
|
30
|
-
export function ToolCard({ use, result, runStreaming, runSucceeded, projectFileNames, onRequestOpenFile }) {
|
|
39
|
+
export function ToolCard({ use, result, runStreaming, runSucceeded, projectFileNames, onRequestOpenFile, retried = false }) {
|
|
31
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 };
|
|
32
44
|
const name = use.name;
|
|
33
45
|
const card = { input: use.input, result: result, runStreaming: runStreaming ?? false, runSucceeded: runSucceeded ?? false };
|
|
34
|
-
const content = renderToolCardBody({ use, card, fileCtx: { projectFileNames, onRequestOpenFile }, registry });
|
|
46
|
+
const content = renderToolCardBody({ use, card, wrapper: presentation.wrapper, fileCtx: { projectFileNames, onRequestOpenFile }, registry });
|
|
35
47
|
if (content === null)
|
|
36
48
|
return null;
|
|
37
49
|
// Tagged once here, at the single dispatch point every branch below funnels through, rather than
|
|
@@ -39,7 +51,7 @@ export function ToolCard({ use, result, runStreaming, runSucceeded, projectFileN
|
|
|
39
51
|
// host's own custom-registered renderer) gets it automatically. `use.id` is this tool call's own
|
|
40
52
|
// stable identifier (unique per call within a message), never reused, so this handle is never
|
|
41
53
|
// ambiguous the way a repeated literal handle would be.
|
|
42
|
-
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 }) }));
|
|
43
55
|
}
|
|
44
56
|
// Hardcoded family cards for tools shipped with this package, keyed by every name each tool goes
|
|
45
57
|
// by. A `Map`, not an object literal, so a tool named `constructor`/`toString` can never resolve
|
|
@@ -89,7 +101,8 @@ function renderToolCardBody(args) {
|
|
|
89
101
|
// `useToolTimeline`: `foldDelegatedWrapperCalls` drops it in favour of that canonical row. This
|
|
90
102
|
// check still covers a host that renders `<ToolCard>` straight from raw events.)
|
|
91
103
|
// Covers the read-only gateway (`execute_readonly_delegated_tool`) too.
|
|
92
|
-
|
|
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)
|
|
93
106
|
return null;
|
|
94
107
|
if (isTodoWriteToolName({ name: name }))
|
|
95
108
|
return _jsx(TodoCard, { todos: parseTodoWriteInput({ input: use.input }), runStreaming: card.runStreaming });
|
|
@@ -107,28 +120,29 @@ function renderToolCardBody(args) {
|
|
|
107
120
|
}
|
|
108
121
|
return _jsx(GenericCard, { name: name, ...card });
|
|
109
122
|
}
|
|
110
|
-
/**
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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] }));
|
|
132
146
|
}
|
|
133
147
|
/**
|
|
134
148
|
* A card for one `page.*`/`daemon.db.*`/`chat.*`/… call — the canonical event
|
|
@@ -138,27 +152,24 @@ function primaryTarget(input) {
|
|
|
138
152
|
* blob this replaces.
|
|
139
153
|
*/
|
|
140
154
|
function DelegatedToolCard({ name, input, result, runStreaming, runSucceeded }) {
|
|
141
|
-
const t = useT();
|
|
142
155
|
// Wrapper form (only reached on a failed wrapper call, per ToolCard's suppression comment):
|
|
143
156
|
// the real id and args are nested under {toolId, input}, not `input` itself.
|
|
144
157
|
const wrapper = (input ?? {});
|
|
145
158
|
const toolId = isDelegatedWrapperToolName({ name: name }) ? (wrapper.toolId ?? name) : name;
|
|
146
159
|
const args = isDelegatedWrapperToolName({ name: name }) ? wrapper.input : input;
|
|
147
|
-
|
|
148
|
-
const isRunning = runStreaming && !result;
|
|
149
|
-
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 ? ' shimmer-text' : ''}`, 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 }) }) }));
|
|
150
161
|
}
|
|
151
162
|
function SearchToolsCard({ input, result, runStreaming, runSucceeded }) {
|
|
152
163
|
const t = useT();
|
|
153
164
|
const obj = (input ?? {});
|
|
154
165
|
const isRunning = runStreaming && !result;
|
|
155
|
-
return (_jsx(ExpandableCard, { className: "op-jini-tool", 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${isRunning ?
|
|
166
|
+
return (_jsx(ExpandableCard, { className: "op-jini-tool", 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${isRunning ? RUNNING_TITLE_CLASS : ''}`, children: t('Search tools') }), obj.query ? _jsx("span", { className: "op-meta", children: obj.query }) : null] }), after: _jsx(FileErrorDetail, { result: result }), children: _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
|
|
156
167
|
}
|
|
157
168
|
function DescribeToolCard({ input, result, runStreaming, runSucceeded }) {
|
|
158
169
|
const t = useT();
|
|
159
170
|
const obj = (input ?? {});
|
|
160
171
|
const isRunning = runStreaming && !result;
|
|
161
|
-
return (_jsx(ExpandableCard, { className: "op-jini-tool", 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${isRunning ?
|
|
172
|
+
return (_jsx(ExpandableCard, { className: "op-jini-tool", 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${isRunning ? RUNNING_TITLE_CLASS : ''}`, children: t('Describe tool') }), obj.id ? _jsx("span", { className: "op-meta", children: obj.id }) : null] }), after: _jsx(FileErrorDetail, { result: result }), children: _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
|
|
162
173
|
}
|
|
163
174
|
function OpenInTabButton({ filePath, ctx }) {
|
|
164
175
|
const t = useT();
|
|
@@ -184,7 +195,7 @@ function FileWriteCard({ input, result, runStreaming, runSucceeded, ctx }) {
|
|
|
184
195
|
const baseName = file.split('/').pop();
|
|
185
196
|
const lines = typeof obj.content === 'string' ? obj.content.split('\n').length : null;
|
|
186
197
|
const isRunning = runStreaming && !result;
|
|
187
|
-
return (_jsxs(ExpandableCard, { className: "op-file", 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${isRunning ?
|
|
198
|
+
return (_jsxs(ExpandableCard, { className: "op-file", 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${isRunning ? RUNNING_TITLE_CLASS : ''}`, children: t('Write') }), _jsxs("span", { className: "op-meta", children: [baseName, lines !== null ? ` · ${t('{n} lines', { n: lines })}` : ''] })] }), after: _jsx(FileErrorDetail, { result: result }), children: [_jsxs("div", { className: "op-card-detail op-card-file-detail", children: [_jsx("code", { className: "op-path", children: file }), _jsx(OpenInTabButton, { filePath: file, ctx: ctx })] }), _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded })] }));
|
|
188
199
|
}
|
|
189
200
|
function FileEditCard({ input, result, runStreaming, runSucceeded, ctx }) {
|
|
190
201
|
const t = useT();
|
|
@@ -194,7 +205,7 @@ function FileEditCard({ input, result, runStreaming, runSucceeded, ctx }) {
|
|
|
194
205
|
const baseName = file.split('/').pop();
|
|
195
206
|
const editCount = Array.isArray(obj.edits) ? obj.edits.length : 1;
|
|
196
207
|
const isRunning = runStreaming && !result;
|
|
197
|
-
return (_jsxs(ExpandableCard, { className: "op-file", 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${isRunning ?
|
|
208
|
+
return (_jsxs(ExpandableCard, { className: "op-file", 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${isRunning ? RUNNING_TITLE_CLASS : ''}`, children: t('Edit') }), _jsxs("span", { className: "op-meta", children: [baseName, " \u00B7 ", editCount, " ", editCount === 1 ? t('change') : t('changes')] })] }), after: _jsx(FileErrorDetail, { result: result }), children: [_jsxs("div", { className: "op-card-detail op-card-file-detail", children: [_jsx("code", { className: "op-path", children: file }), _jsx(OpenInTabButton, { filePath: file, ctx: ctx })] }), _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded })] }));
|
|
198
209
|
}
|
|
199
210
|
function FileReadCard({ input, result, runStreaming, runSucceeded, ctx }) {
|
|
200
211
|
const t = useT();
|
|
@@ -203,7 +214,7 @@ function FileReadCard({ input, result, runStreaming, runSucceeded, ctx }) {
|
|
|
203
214
|
// See OpenInTabButton's baseName comment: `.split('/').pop()` is never undefined.
|
|
204
215
|
const baseName = file.split('/').pop();
|
|
205
216
|
const isRunning = runStreaming && !result;
|
|
206
|
-
return (_jsxs(ExpandableCard, { className: "op-file", 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${isRunning ?
|
|
217
|
+
return (_jsxs(ExpandableCard, { className: "op-file", 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${isRunning ? RUNNING_TITLE_CLASS : ''}`, children: t('Read') }), _jsx("span", { className: "op-meta", children: baseName })] }), after: _jsx(FileErrorDetail, { result: result }), children: [_jsxs("div", { className: "op-card-detail op-card-file-detail", children: [_jsx("code", { className: "op-path", children: file }), _jsx(OpenInTabButton, { filePath: file, ctx: ctx })] }), _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded })] }));
|
|
207
218
|
}
|
|
208
219
|
function BashCard({ input, result, runStreaming, runSucceeded }) {
|
|
209
220
|
const t = useT();
|
|
@@ -211,7 +222,7 @@ function BashCard({ input, result, runStreaming, runSucceeded }) {
|
|
|
211
222
|
const command = obj.command ?? '';
|
|
212
223
|
const desc = obj.description;
|
|
213
224
|
const isRunning = runStreaming && !result;
|
|
214
|
-
return (_jsx(ExpandableCard, { className: "op-bash", 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${isRunning ?
|
|
225
|
+
return (_jsx(ExpandableCard, { className: "op-bash", 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${isRunning ? RUNNING_TITLE_CLASS : ''}`, children: t('Bash') }), desc ? _jsx("span", { className: "op-meta op-desc", children: desc }) : null] }), children: _jsx(ToolCallDetail, { input: command, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
|
|
215
226
|
}
|
|
216
227
|
function GlobCard({ input, result, runStreaming, runSucceeded }) {
|
|
217
228
|
const t = useT();
|
|
@@ -235,8 +246,7 @@ function WebSearchCard({ input, result, runStreaming, runSucceeded }) {
|
|
|
235
246
|
}
|
|
236
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. */
|
|
237
248
|
function GenericCard({ name, input, result, runStreaming, runSucceeded }) {
|
|
238
|
-
|
|
239
|
-
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 }) }));
|
|
240
250
|
}
|
|
241
251
|
/**
|
|
242
252
|
* The one row shell every card shares: a real `<button>` head (Enter/Space toggle it) that reports
|
|
@@ -254,7 +264,14 @@ function ExpandableCard({ className, head, after, call, children }) {
|
|
|
254
264
|
}
|
|
255
265
|
/** The collapsed row's stand-in for its panel — see {@link ExpandableCard} and `describeCollapsedToolCall`. */
|
|
256
266
|
function CollapsedReaderText({ call }) {
|
|
257
|
-
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]);
|
|
258
275
|
return _jsx("span", { className: "op-card-reader-text", hidden: true, children: text });
|
|
259
276
|
}
|
|
260
277
|
/** The call's input, its result (or why there is none), and any extra `children` — see `describeToolCallDetail` for the redaction and formatting rules. */
|
|
@@ -265,6 +282,13 @@ function ToolCallDetail({ input, result, runStreaming, runSucceeded, children })
|
|
|
265
282
|
const detail = describeToolCallDetail({ input: input, result: result }, { runStreaming: runStreaming, runSucceeded: runSucceeded, maxChars: Infinity });
|
|
266
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] }));
|
|
267
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
|
+
}
|
|
268
292
|
function ResultBadge({ result, runStreaming, runSucceeded }) {
|
|
269
293
|
const t = useT();
|
|
270
294
|
if (!result && runStreaming)
|
|
@@ -272,7 +296,7 @@ function ResultBadge({ result, runStreaming, runSucceeded }) {
|
|
|
272
296
|
if (!result && !runSucceeded)
|
|
273
297
|
return (_jsx("span", { className: "op-status op-status-error", title: t('Error'), children: _jsx(Icon, { name: "close", size: 14 }) }));
|
|
274
298
|
if (result?.isError)
|
|
275
|
-
return
|
|
299
|
+
return _jsx(ErrorBadge, { result: result });
|
|
276
300
|
return (_jsx("span", { className: "op-status op-status-ok", title: t('Done'), children: _jsx(Icon, { name: "check", size: 14 }) }));
|
|
277
301
|
}
|
|
278
302
|
/**
|
|
@@ -294,7 +318,23 @@ function ToolResultMedia({ result }) {
|
|
|
294
318
|
const images = blocks.filter((block) => block.type === 'image');
|
|
295
319
|
if (images.length === 0)
|
|
296
320
|
return null;
|
|
297
|
-
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 })] }));
|
|
298
338
|
}
|
|
299
339
|
function FileErrorDetail({ result }) {
|
|
300
340
|
if (!result?.isError)
|
|
@@ -304,26 +344,6 @@ function FileErrorDetail({ result }) {
|
|
|
304
344
|
const detail = describeToolCallDetail({ input: null, result }, { maxChars: COLLAPSED_DETAIL_MAX_CHARS });
|
|
305
345
|
return detail.outputText === null ? null : _jsx("pre", { className: "op-output", children: detail.outputText });
|
|
306
346
|
}
|
|
307
|
-
function describeInput(input) {
|
|
308
|
-
if (input == null)
|
|
309
|
-
return '';
|
|
310
|
-
if (typeof input === 'string')
|
|
311
|
-
return input;
|
|
312
|
-
if (typeof input !== 'object')
|
|
313
|
-
return String(input);
|
|
314
|
-
const obj = input;
|
|
315
|
-
for (const key of ['file_path', 'path', 'pattern', 'url', 'query', 'name', 'command']) {
|
|
316
|
-
const v = obj[key];
|
|
317
|
-
if (typeof v === 'string')
|
|
318
|
-
return v;
|
|
319
|
-
}
|
|
320
|
-
try {
|
|
321
|
-
return JSON.stringify(obj);
|
|
322
|
-
}
|
|
323
|
-
catch {
|
|
324
|
-
return '';
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
347
|
/**
|
|
328
348
|
* A head's one-line summary of an arbitrary argument. Heads stay mounted for every collapsed row,
|
|
329
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,4 +1,4 @@
|
|
|
1
|
-
const SLASH_QUERY_RE = /^\/([^\s/]*)(
|
|
1
|
+
const SLASH_QUERY_RE = /^\/([^\s/]*)(\s+)?/;
|
|
2
2
|
/**
|
|
3
3
|
* Open Design's active slash-token rule (`/^\/([^\s/]*)$/`), extended to admit one optional
|
|
4
4
|
* trailing argument while staying anchored end-to-end. The command segment itself is unchanged —
|
|
@@ -9,13 +9,13 @@ export function parseComposerSlashQuery({ draft }) {
|
|
|
9
9
|
const match = SLASH_QUERY_RE.exec(draft);
|
|
10
10
|
if (!match)
|
|
11
11
|
return null;
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return { command: command, argument: separator === undefined ? null :
|
|
12
|
+
// Group 1 always participates, including the empty command; without a separator the entire
|
|
13
|
+
// draft must be the command. With one, the greedy separator consumes all leading whitespace.
|
|
14
|
+
// This preserves the whole-draft grammar without overlapping separator/rest repetitions.
|
|
15
|
+
const [, command, separator] = match;
|
|
16
|
+
if (separator === undefined && match[0].length !== draft.length)
|
|
17
|
+
return null;
|
|
18
|
+
return { command: command, argument: separator === undefined ? null : draft.slice(match[0].length) };
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* Once a separator has been typed, only an item whose declared `command` exactly equals the typed
|
|
@@ -28,14 +28,13 @@ function resolveAgentPluginStrip({ messages }, { conversationId }) {
|
|
|
28
28
|
/** One per-chat usage strip; removing a name persists its exclusion for future turns. */
|
|
29
29
|
export function AgentPluginUsage({ conversationId, messages }) {
|
|
30
30
|
const t = useT();
|
|
31
|
-
const [
|
|
31
|
+
const [, setRevision] = useState(0);
|
|
32
32
|
useEffect(() => {
|
|
33
33
|
const refresh = () => setRevision(value => value + 1);
|
|
34
34
|
window.addEventListener(AGENT_PLUGIN_STATE_CHANGED, refresh);
|
|
35
35
|
window.addEventListener('storage', refresh);
|
|
36
36
|
return () => { window.removeEventListener(AGENT_PLUGIN_STATE_CHANGED, refresh); window.removeEventListener('storage', refresh); };
|
|
37
37
|
}, []);
|
|
38
|
-
void revision;
|
|
39
38
|
const strip = resolveAgentPluginStrip({ messages }, { conversationId });
|
|
40
39
|
if (!strip)
|
|
41
40
|
return null;
|
|
@@ -10,5 +10,5 @@ export declare function runtimeAgentStatus({ agent }: {
|
|
|
10
10
|
* @complexity Time: O(n) in the runtime inventory; space: O(n) for ordering.
|
|
11
11
|
* @overallScore 100/100
|
|
12
12
|
*/
|
|
13
|
-
export declare function AgentRuntimePicker({ agents, value, onChange, onRescan, scanning, daemonOnline, placement, executionMode, apiModeAvailable, onExecutionModeChange, byokRuntime, onByokModelChange, agentIconBasePath, }: AgentRuntimePickerProps): import("react").JSX.Element;
|
|
13
|
+
export declare function AgentRuntimePicker({ agents, nativeToolPolicy, value, onChange, onRescan, scanning, daemonOnline, placement, executionMode, apiModeAvailable, onExecutionModeChange, byokRuntime, onByokModelChange, agentIconBasePath, }: AgentRuntimePickerProps): import("react").JSX.Element;
|
|
14
14
|
//# sourceMappingURL=AgentRuntimePicker.d.ts.map
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
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';
|
|
7
|
+
import { assessNativeToolPolicy } from '@jini-ai/agent-runtime/native-tool-policy';
|
|
5
8
|
import { useT } from '../../../hooks/context.js';
|
|
6
9
|
import { definedProps } from '../../../util/defined-props.js';
|
|
7
10
|
import { RuntimeDropUp } from './RuntimeDropUp.js';
|
|
@@ -17,18 +20,37 @@ export function runtimeAgentStatus({ agent }) {
|
|
|
17
20
|
return 'Installed · sign-in required';
|
|
18
21
|
return agent.version ?? 'Installed';
|
|
19
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
|
+
}
|
|
31
|
+
/** Selection preflight uses the launch assessor; it never substitutes for enforcement. */
|
|
32
|
+
function NativeToolPolicyWarning({ agent, policy, isApi }) {
|
|
33
|
+
const t = useT();
|
|
34
|
+
if (!agent || isApi)
|
|
35
|
+
return null;
|
|
36
|
+
const warning = policy ? assessNativeToolPolicy({ def: agent, policy }) : null;
|
|
37
|
+
return _jsxs(_Fragment, { children: [agent.fullCliAccessNote ? _jsx(FullAccessNoteTip, { note: agent.fullCliAccessNote, t: t }) : null, warning ? _jsx("p", { role: "alert", children: t(warning) }) : null] });
|
|
38
|
+
}
|
|
20
39
|
function RuntimeModeButtons({ executionMode, daemonOnline, apiModeAvailable, onExecutionModeChange, t, }) {
|
|
21
|
-
|
|
40
|
+
const localModeLabel = executionMode === 'local' ? t('active') : '';
|
|
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] })] }));
|
|
22
42
|
}
|
|
23
43
|
function RuntimeAgentList({ orderedAgents, value, onChange, agentIconBasePath, t, }) {
|
|
44
|
+
const noteIdPrefix = useId();
|
|
24
45
|
if (orderedAgents.length === 0) {
|
|
25
46
|
return _jsx("div", { className: "jini-runtime-empty", children: t('No available code agents') });
|
|
26
47
|
}
|
|
27
|
-
return (_jsx(_Fragment, { children: orderedAgents.map((agent) => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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] }));
|
|
32
54
|
}
|
|
33
55
|
/**
|
|
34
56
|
* The composer bar's "Model ⌄" and "Effort ⌄" drop-ups for the selected CLI. These replace the
|
|
@@ -127,7 +149,7 @@ function RuntimePopover({ picker, value, onChange, onRescan, scanning, daemonOnl
|
|
|
127
149
|
* @complexity Time: O(n) in the runtime inventory; space: O(n) for ordering.
|
|
128
150
|
* @overallScore 100/100
|
|
129
151
|
*/
|
|
130
|
-
export function AgentRuntimePicker({ agents, value, onChange, onRescan, scanning = false, daemonOnline = true, placement = 'up', executionMode = 'local', apiModeAvailable = false, onExecutionModeChange, byokRuntime, onByokModelChange,
|
|
152
|
+
export function AgentRuntimePicker({ agents, nativeToolPolicy, value, onChange, onRescan, scanning = false, daemonOnline = true, placement = 'up', executionMode = 'local', apiModeAvailable = false, onExecutionModeChange, byokRuntime, onByokModelChange,
|
|
131
153
|
// No default here (was '/agent-icons'): omitted means "let AgentIcon fall back to its own
|
|
132
154
|
// bundled icon set" — see AgentIcon.tsx's BUNDLED_ICON_URLS. A host that still wants to vendor
|
|
133
155
|
// its own asset directory can pass this explicitly, same as before.
|
|
@@ -140,6 +162,6 @@ agentIconBasePath, }) {
|
|
|
140
162
|
// operator on BYOK saw a CLI named as "selected" and a model reading "Default (CLI config)"
|
|
141
163
|
// while their messages went to a provider over an API key.
|
|
142
164
|
const isApi = executionMode === 'api';
|
|
143
|
-
return (_jsxs("div", { className: "jini-runtime-picker", children: [_jsx(RuntimeTrigger, { picker: picker, isApi: isApi, byokRuntime: byokRuntime, agentIconBasePath: agentIconBasePath, t: t }), isApi ? null : (_jsx(RuntimeBarMenus, { selectedAgent: picker.selectedAgent, value: value, onChange: onChange, modelLabel: picker.modelLabel, t: t })), _jsx(RuntimePopover, { picker: picker, value: value, onChange: onChange, onRescan: onRescan, scanning: scanning, daemonOnline: daemonOnline, executionMode: executionMode, apiModeAvailable: apiModeAvailable, onExecutionModeChange: onExecutionModeChange, byokRuntime: byokRuntime, onByokModelChange: onByokModelChange, agentIconBasePath: agentIconBasePath, t: t })] }));
|
|
165
|
+
return (_jsxs("div", { className: "jini-runtime-picker", children: [_jsx(RuntimeTrigger, { picker: picker, isApi: isApi, byokRuntime: byokRuntime, agentIconBasePath: agentIconBasePath, t: t }), isApi ? null : (_jsx(RuntimeBarMenus, { selectedAgent: picker.selectedAgent, value: value, onChange: onChange, modelLabel: picker.modelLabel, t: t })), _jsx(NativeToolPolicyWarning, { agent: picker.selectedAgent, policy: nativeToolPolicy, isApi: isApi }), _jsx(RuntimePopover, { picker: picker, value: value, onChange: onChange, onRescan: onRescan, scanning: scanning, daemonOnline: daemonOnline, executionMode: executionMode, apiModeAvailable: apiModeAvailable, onExecutionModeChange: onExecutionModeChange, byokRuntime: byokRuntime, onByokModelChange: onByokModelChange, agentIconBasePath: agentIconBasePath, t: t })] }));
|
|
144
166
|
}
|
|
145
167
|
//# sourceMappingURL=AgentRuntimePicker.js.map
|
|
@@ -25,7 +25,8 @@ function defaultHeader(title, onReset, t) {
|
|
|
25
25
|
return (_jsxs("div", { className: "jini-chat-pane__header", children: [_jsxs("div", { className: "jini-chat-pane__heading", children: [_jsx("span", { className: "jini-chat-pane__eyebrow", children: t('Workspace chat') }), _jsx("h1", { className: "jini-chat-pane__title", children: title ?? t('Chat') })] }), _jsx("button", { type: "button", className: "jini-chat-pane__new-thread", onClick: onReset, children: t('New thread') })] }));
|
|
26
26
|
}
|
|
27
27
|
function chatPaneClassName(variant, className) {
|
|
28
|
-
|
|
28
|
+
const suffix = className ? ' ' + className : '';
|
|
29
|
+
return `jini-chat-pane jini-chat-pane--${variant}${suffix}`;
|
|
29
30
|
}
|
|
30
31
|
function resolveRuntimeAccessView(runtimeAccess, injectedAgents, scanningAgents, daemonOnline, onRescanAgents, inventory) {
|
|
31
32
|
if (runtimeAccess === undefined) {
|
|
@@ -268,7 +269,7 @@ function useComposerHandlePublisher({ pane }, { composerHandle }) {
|
|
|
268
269
|
composerDraftRef.current = pane.composer.draft;
|
|
269
270
|
useEffect(() => {
|
|
270
271
|
if (!composerHandle)
|
|
271
|
-
return;
|
|
272
|
+
return undefined;
|
|
272
273
|
composerHandle.current = {
|
|
273
274
|
insertText: (text) => {
|
|
274
275
|
pane.composer.setDraft(appendComposerDiscovery({ draft: composerDraftRef.current, insertText: text }));
|
|
@@ -301,11 +302,11 @@ function buildChatPaneSlots({ surfaceSlots, embed, renderRuntimePicker }, { lead
|
|
|
301
302
|
footerAccessories: embed ? undefined : renderRuntimePicker(),
|
|
302
303
|
} });
|
|
303
304
|
}
|
|
304
|
-
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, }) {
|
|
305
306
|
if (pane.conversation.messages.length === 0 && emptyState) {
|
|
306
307
|
return (_jsxs("div", { className: "jini-message-list jini-message-list--empty", children: [emptyState, embed && _jsx(ChatPaneSuggestionsRow, { suggestions: suggestions, onSelect: onSelectSuggestion, t: t })] }));
|
|
307
308
|
}
|
|
308
|
-
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 }) }));
|
|
309
310
|
}
|
|
310
311
|
/** The controls-area suggestions row shows on every workspace surface, and on the embed surface
|
|
311
312
|
* only when the empty transcript has no `emptyState` to host it instead. */
|
|
@@ -396,12 +397,13 @@ export function ChatPane(props) {
|
|
|
396
397
|
onExecutionModeChange: p.onExecutionModeChange,
|
|
397
398
|
byokRuntime: p.byokRuntime,
|
|
398
399
|
onByokModelChange: p.onByokModelChange,
|
|
400
|
+
nativeToolPolicy: p.nativeToolPolicy,
|
|
399
401
|
} }) })),
|
|
400
402
|
}, { leadingAccessory: p.leadingAccessory });
|
|
401
403
|
const { rootRef, controlsRef } = useChatPaneControlsHeight();
|
|
402
404
|
const selectSuggestion = useChatPaneSuggestion({ rootRef, setDraft: pane.composer.setDraft });
|
|
403
405
|
const openLinkInNewTab = useChatPaneNewTabLinks();
|
|
404
406
|
const { pendingPrompts, projectFileNameSet } = useChatPaneTranscriptProps({ pane }, definedProps({ source: { projectFileNames: p.projectFileNames } }));
|
|
405
|
-
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] }));
|
|
406
408
|
}
|
|
407
409
|
//# sourceMappingURL=ChatPane.js.map
|