@jini-ai/chat 0.6.6 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +129 -1
- package/dist/core/accepted-input.d.ts +54 -0
- package/dist/core/accepted-input.js +165 -0
- package/dist/core/agent-plugin-chat-state.d.ts +43 -0
- package/dist/core/agent-plugin-chat-state.js +48 -0
- package/dist/core/assistant-client/a2ui-action-poster.js +32 -28
- package/dist/core/assistant-client/messages.d.ts +1 -1
- package/dist/core/assistant-client/messages.js +2 -2
- package/dist/core/assistant-client/transport.d.ts +2 -2
- package/dist/core/assistant-client/transport.js +187 -98
- package/dist/core/assistant-content.d.ts +2 -1
- package/dist/core/assistant-content.js +9 -11
- package/dist/core/browser/page-actions.js +4 -2
- package/dist/core/composer-history.d.ts +16 -9
- package/dist/core/composer-history.js +28 -16
- package/dist/core/conversation-title.d.ts +3 -0
- package/dist/core/conversation-title.js +3 -0
- package/dist/core/durable-projection.js +20 -13
- package/dist/core/durable-runs/continuation.d.ts +4 -0
- package/dist/core/durable-runs/continuation.js +31 -6
- package/dist/core/durable-runs/ledger-contracts.d.ts +2 -1
- package/dist/core/durable-runs/ports.d.ts +6 -2
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/messages.d.ts +2 -0
- package/dist/core/partial-json.d.ts +6 -7
- package/dist/core/partial-json.js +81 -47
- package/dist/core/persistence/title.js +4 -2
- package/dist/core/question-answer.d.ts +23 -0
- package/dist/core/question-answer.js +66 -0
- package/dist/core/question-form/format.d.ts +6 -6
- package/dist/core/question-form/format.js +16 -17
- package/dist/core/question-form/normalize.d.ts +18 -0
- package/dist/core/question-form/normalize.js +112 -122
- package/dist/core/question-form/scan.js +141 -203
- package/dist/core/run-activity.js +62 -49
- package/dist/core/run-events/entry.d.ts +10 -1
- package/dist/core/run-events/entry.js +117 -87
- package/dist/core/todos.d.ts +1 -1
- package/dist/core/todos.js +60 -85
- package/dist/core/tool-events.js +14 -12
- package/dist/core/transcript.js +62 -16
- package/dist/core/turn-usage.js +54 -32
- package/dist/core/user-text-redaction.js +3 -0
- package/dist/core/util/manifest.js +77 -82
- package/dist/core/util/markdown-context.js +51 -53
- package/dist/core/util/parser.js +136 -144
- package/dist/core/util/pointer.js +16 -16
- package/dist/core/util/strip.js +57 -67
- package/dist/core/util/validate.js +15 -17
- package/dist/embed-widget/browser/session-store.js +21 -12
- package/dist/embed-widget/transports/fetch-sse.js +108 -95
- package/dist/embed-widget/vanilla/index.js +81 -70
- package/dist/react/agent-bridge/frontend-session-bridge.js +80 -52
- package/dist/react/components/AttachmentPreviewModal.js +25 -1
- package/dist/react/components/ChatSettingsDialog.d.ts +13 -0
- package/dist/react/components/ChatSettingsDialog.hooks.d.ts +115 -0
- package/dist/react/components/ChatSettingsDialog.hooks.js +40 -0
- package/dist/react/components/ChatSettingsDialog.js +72 -0
- package/dist/react/components/ChatSettingsSegmented.d.ts +18 -0
- package/dist/react/components/ChatSettingsSegmented.js +11 -0
- package/dist/react/components/Composer.d.ts +5 -1
- package/dist/react/components/Composer.js +126 -82
- package/dist/react/components/ConversationList.js +25 -39
- package/dist/react/components/ConversationTitleEditor.d.ts +27 -0
- package/dist/react/components/ConversationTitleEditor.js +54 -0
- package/dist/react/components/Icon.d.ts +1 -1
- package/dist/react/components/Icon.js +25 -45
- package/dist/react/components/Markdown.js +134 -123
- package/dist/react/components/McpUiSurfaceCard.d.ts +2 -2
- package/dist/react/components/McpUiSurfaceCard.js +30 -9
- package/dist/react/components/MessageList.d.ts +13 -13
- package/dist/react/components/MessageList.hooks.d.ts +6 -1
- package/dist/react/components/MessageList.hooks.js +19 -3
- package/dist/react/components/MessageList.js +34 -10
- package/dist/react/components/MessageRow.d.ts +1 -1
- package/dist/react/components/MessageRow.js +75 -47
- package/dist/react/components/MessageRow.usage.js +16 -11
- package/dist/react/components/MidRunUserMessage.js +16 -3
- package/dist/react/components/QuestionForm.js +148 -76
- package/dist/react/components/QuestionsPanel.js +1 -1
- package/dist/react/components/TodoCard.js +12 -6
- package/dist/react/components/ToolCallGroup.d.ts +4 -3
- package/dist/react/components/ToolCallGroup.js +7 -5
- package/dist/react/components/ToolCard.js +47 -40
- package/dist/react/components/composer-discovery.js +6 -1
- package/dist/react/components/mcp-ui-surface-state.d.ts +6 -11
- package/dist/react/components/mcp-ui-surface-state.js +23 -8
- package/dist/react/ext-event-renderer-registry.d.ts +8 -0
- package/dist/react/ext-event-renderer-registry.js +5 -2
- package/dist/react/features/chat-pane/components/AgentPluginUsage.d.ts +7 -0
- package/dist/react/features/chat-pane/components/AgentPluginUsage.js +45 -0
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +55 -19
- package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
- package/dist/react/features/chat-pane/components/ChatPane.js +192 -74
- package/dist/react/features/chat-pane/components/RuntimeDropUp.d.ts +29 -0
- package/dist/react/features/chat-pane/components/RuntimeDropUp.js +45 -0
- package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +80 -47
- package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.js +44 -40
- package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +6 -6
- package/dist/react/features/chat-pane/create-mid-run-message-poster.js +8 -8
- package/dist/react/features/chat-pane/create-typed-answer-poster.d.ts +3 -3
- package/dist/react/features/chat-pane/create-typed-answer-poster.js +3 -3
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.d.ts +6 -1
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +11 -5
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +12 -7
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +340 -149
- package/dist/react/features/chat-pane/hooks/useChatPaneControlsHeight.hooks.js +7 -6
- package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.js +28 -10
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.d.ts +3 -3
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +60 -12
- package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.d.ts +49 -0
- package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.js +143 -0
- package/dist/react/features/chat-pane/model-options.d.ts +14 -0
- package/dist/react/features/chat-pane/model-options.js +45 -25
- package/dist/react/features/chat-pane/rules.d.ts +8 -9
- package/dist/react/features/chat-pane/rules.js +84 -46
- package/dist/react/features/chat-pane/styles.d.ts +1 -1
- package/dist/react/features/chat-pane/styles.js +152 -36
- package/dist/react/features/chat-pane/types.d.ts +19 -9
- package/dist/react/hooks/assistant-chats.js +12 -5
- package/dist/react/hooks/composer-draft-cache.js +116 -118
- package/dist/react/hooks/composer-history-storage.js +5 -5
- package/dist/react/hooks/fab-position.js +7 -4
- package/dist/react/hooks/useAnsweredCards.d.ts +21 -4
- package/dist/react/hooks/useAnsweredCards.js +38 -11
- package/dist/react/hooks/useArtifactStream.js +28 -22
- package/dist/react/hooks/useComposer.d.ts +20 -0
- package/dist/react/hooks/useComposer.js +22 -9
- package/dist/react/hooks/useComposerHistory.hooks.d.ts +6 -1
- package/dist/react/hooks/useComposerHistory.hooks.js +39 -19
- package/dist/react/hooks/useComposerHistoryKeys.hooks.js +24 -14
- package/dist/react/hooks/useConversation.d.ts +7 -0
- package/dist/react/hooks/useConversation.js +23 -10
- package/dist/react/hooks/useExtEventGroups.js +39 -39
- package/dist/react/hooks/useQuestionForms.js +21 -26
- package/dist/react/hooks/useRunStream.js +10 -2
- package/dist/react/index.d.ts +7 -1
- package/dist/react/index.js +9 -0
- package/dist/react/message-blocks.d.ts +6 -4
- package/dist/react/message-blocks.js +94 -72
- package/dist/react/slots.d.ts +2 -2
- package/dist/react/tool-call-display.d.ts +12 -6
- package/dist/react/tool-call-display.js +53 -24
- package/dist/server/durable-runs/recover.js +132 -80
- package/dist/server/run-finalizer/entry.js +25 -15
- package/dist/store/legacy/sqlite/conversations.d.ts +11 -11
- package/dist/store/legacy/sqlite/conversations.js +69 -98
- package/dist/store/legacy/sqlite/messages.d.ts +10 -2
- package/dist/store/legacy/sqlite/messages.js +113 -71
- package/dist/store/runtime/chat-history-store.js +9 -3
- package/dist/store/runtime/durable-run-store.d.ts +2 -2
- package/dist/store/runtime/durable-run-store.js +165 -117
- package/dist/store/runtime/history-tools.js +118 -82
- package/dist/store/runtime/run-ledger.d.ts +4 -1
- package/dist/store/runtime/run-ledger.js +56 -16
- package/dist/store/sql/cursor.js +38 -18
- package/dist/store/sql/store.d.ts +18 -0
- package/dist/store/sql/store.js +46 -15
- package/package.json +18 -16
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { CREDENTIAL_CARD_GUIDANCE, SECRET_REDACTED_NOTICE } from '../../core/user-text-redaction.js';
|
|
3
3
|
import { useUserTextGuard } from '../hooks/useUserTextGuard.js';
|
|
4
4
|
/**
|
|
@@ -21,34 +21,46 @@ export const QuestionForm = forwardRef(function QuestionForm({ form: incomingFor
|
|
|
21
21
|
const t = useT();
|
|
22
22
|
const form = useMemo(() => withoutSecretQuestionDefaults({ form: incomingForm }), [incomingForm]);
|
|
23
23
|
const guard = useUserTextGuard({ ...(redactUserText ? { redactUserText } : {}), ...(onSecretRedacted ? { onSecretRedacted } : {}) });
|
|
24
|
+
const locked = !interactive || !onSubmit || submittedAnswers !== undefined;
|
|
25
|
+
const state = useQuestionAnswers({ form, submittedAnswers, draftAnswers, locked }, { onDraftChange, onAnswerChange });
|
|
26
|
+
const currentAnswers = submittedAnswers ? redactQuestionAnswers({ form, answers: submittedAnswers }).answers : state.answers;
|
|
27
|
+
const ready = answersReady(form, currentAnswers);
|
|
28
|
+
function handleSubmit() {
|
|
29
|
+
if (locked || !onSubmit)
|
|
30
|
+
return;
|
|
31
|
+
if (!ready)
|
|
32
|
+
return;
|
|
33
|
+
const safe = redactQuestionAnswers({ form, answers: state.answers }, { ...(redactUserText ? { redactUserText } : {}), onSecretRedacted: guard.notifyRedaction });
|
|
34
|
+
state.setAnswers(safe.answers);
|
|
35
|
+
const files = collectFileSubmissions(form, state.fileAnswers);
|
|
36
|
+
if (files.length > 0)
|
|
37
|
+
onSubmit(formatFormAnswers({ form: form, answers: safe.answers }), safe.answers, files);
|
|
38
|
+
else
|
|
39
|
+
onSubmit(formatFormAnswers({ form: form, answers: safe.answers }), safe.answers);
|
|
40
|
+
}
|
|
41
|
+
function handleSkipAll() {
|
|
42
|
+
if (locked || !onSubmit)
|
|
43
|
+
return;
|
|
44
|
+
const empty = {};
|
|
45
|
+
onSubmit(formatFormAnswers({ form: form, answers: empty }), empty);
|
|
46
|
+
}
|
|
47
|
+
useImperativeHandle(ref, () => ({ submit: handleSubmit, skipAll: handleSkipAll }));
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
onReadyChange?.(!locked && ready);
|
|
50
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
51
|
+
}, [onReadyChange, locked, ready]);
|
|
52
|
+
return (_jsxs("div", { className: `question-form${locked ? ' question-form-locked' : ''}`, "data-form-id": form.id, children: [_jsx(QuestionFormHead, { form: form, locked: locked }), _jsx("div", { className: "question-form-body", children: form.questions.map((q) => (_jsx(QuestionField, { q: q, value: currentAnswers[q.id], formId: form.id, locked: locked, t: t, ...state.handlers }, q.id))) }), guard.secretRedacted ? _jsx("div", { role: "status", children: t(SECRET_REDACTED_NOTICE) }) : null, hideInternalSubmit ? null : _jsx(QuestionFormFoot, { locked: locked, answered: submittedAnswers !== undefined, ready: ready, submitLabel: form.submitLabel, onSubmit: handleSubmit })] }));
|
|
53
|
+
});
|
|
54
|
+
/** Owns the editable answers plus the picked files; every edit is a no-op once the form is locked. */
|
|
55
|
+
function useQuestionAnswers({ form, submittedAnswers, draftAnswers, locked }, { onDraftChange, onAnswerChange } = {}) {
|
|
24
56
|
const initial = useMemo(() => buildInitialState(form, submittedAnswers, draftAnswers), [form, submittedAnswers, draftAnswers]);
|
|
25
57
|
const [answers, setAnswers] = useState(initial);
|
|
26
58
|
const [fileAnswers, setFileAnswers] = useState({});
|
|
27
|
-
const locked = !interactive || !onSubmit || submittedAnswers !== undefined;
|
|
28
|
-
const currentAnswers = submittedAnswers ? redactQuestionAnswers({ form, answers: submittedAnswers }).answers : answers;
|
|
29
59
|
useEffect(() => {
|
|
30
60
|
setFileAnswers({});
|
|
31
61
|
}, [form.id]);
|
|
32
62
|
useEffect(() => {
|
|
33
|
-
setAnswers((prev) =>
|
|
34
|
-
let changed = false;
|
|
35
|
-
const next = { ...prev };
|
|
36
|
-
for (const q of form.questions) {
|
|
37
|
-
if (next[q.id] !== undefined)
|
|
38
|
-
continue;
|
|
39
|
-
changed = true;
|
|
40
|
-
if (submittedAnswers && submittedAnswers[q.id] !== undefined) {
|
|
41
|
-
next[q.id] = canonicalizeQuestionValue(q, submittedAnswers[q.id]);
|
|
42
|
-
}
|
|
43
|
-
else if (q.defaultValue !== undefined) {
|
|
44
|
-
next[q.id] = canonicalizeQuestionValue(q, q.defaultValue);
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
next[q.id] = emptyQuestionValue(q);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return changed ? next : prev;
|
|
51
|
-
});
|
|
63
|
+
setAnswers((prev) => backfillAnswers(prev, form, submittedAnswers));
|
|
52
64
|
}, [form, submittedAnswers]);
|
|
53
65
|
function update(id, value) {
|
|
54
66
|
if (locked)
|
|
@@ -78,60 +90,111 @@ export const QuestionForm = forwardRef(function QuestionForm({ form: incomingFor
|
|
|
78
90
|
const fixed = current.filter((entry) => questionValueIsKnown(q, entry));
|
|
79
91
|
update(q.id, [...fixed, ...splitCustomEntries(raw)]);
|
|
80
92
|
}
|
|
81
|
-
function
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const safe = redactQuestionAnswers({ form, answers }, { ...(redactUserText ? { redactUserText } : {}), onSecretRedacted: guard.notifyRedaction });
|
|
87
|
-
setAnswers(safe.answers);
|
|
88
|
-
const files = collectFileSubmissions(form, fileAnswers);
|
|
89
|
-
if (files.length > 0)
|
|
90
|
-
onSubmit(formatFormAnswers({ form: form, answers: safe.answers }), safe.answers, files);
|
|
91
|
-
else
|
|
92
|
-
onSubmit(formatFormAnswers({ form: form, answers: safe.answers }), safe.answers);
|
|
93
|
-
}
|
|
94
|
-
function handleSkipAll() {
|
|
95
|
-
if (locked || !onSubmit)
|
|
96
|
-
return;
|
|
97
|
-
const empty = {};
|
|
98
|
-
onSubmit(formatFormAnswers({ form: form, answers: empty }), empty);
|
|
93
|
+
function selectFiles(q, list) {
|
|
94
|
+
const files = Array.from(list ?? []);
|
|
95
|
+
const names = files.map((file) => file.name);
|
|
96
|
+
setFileAnswers((current) => ({ ...current, [q.id]: files }));
|
|
97
|
+
update(q.id, q.multiple ? names : (names[0] ?? ''));
|
|
99
98
|
}
|
|
99
|
+
return { answers, setAnswers, fileAnswers, handlers: { update, toggleCheckbox, updateCheckboxCustom, selectFiles } };
|
|
100
|
+
}
|
|
101
|
+
function answersReady(form, answers) {
|
|
100
102
|
const withinSelectionLimits = form.questions.every((q) => {
|
|
101
103
|
if (q.type !== 'checkbox' || q.maxSelections === undefined)
|
|
102
104
|
return true;
|
|
103
|
-
const v =
|
|
105
|
+
const v = answers[q.id];
|
|
104
106
|
return !Array.isArray(v) || v.length <= q.maxSelections;
|
|
105
107
|
});
|
|
106
108
|
const requiredAnswered = form.questions.every((q) => {
|
|
107
109
|
if (q.required !== true)
|
|
108
110
|
return true;
|
|
109
|
-
const v =
|
|
111
|
+
const v = answers[q.id];
|
|
110
112
|
if (Array.isArray(v))
|
|
111
113
|
return v.length > 0;
|
|
112
114
|
return typeof v === 'string' && v.trim().length > 0;
|
|
113
115
|
});
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
116
|
+
return withinSelectionLimits && requiredAnswered;
|
|
117
|
+
}
|
|
118
|
+
function QuestionFormHead({ form, locked }) {
|
|
119
|
+
const t = useT();
|
|
120
|
+
return (_jsxs("div", { className: "question-form-head", children: [_jsx("span", { className: "question-form-icon", "aria-hidden": true, children: "?" }), _jsxs("div", { className: "question-form-titles", children: [_jsx("div", { className: "question-form-title", children: form.title }), form.description ? _jsx("div", { className: "question-form-desc", children: form.description }) : null] }), locked ? _jsx("span", { className: "question-form-pill", children: t('Answered') }) : null] }));
|
|
121
|
+
}
|
|
122
|
+
function QuestionFormFoot({ locked, answered, ready, submitLabel, onSubmit }) {
|
|
123
|
+
const t = useT();
|
|
124
|
+
return (_jsxs("div", { className: "question-form-foot", children: [locked ? _jsx("span", { className: "qf-locked-note", children: answered ? t('You answered this') : t('A newer message answered this') }) : _jsx("span", { className: "qf-hint", children: t('Answer above, then continue') }), !locked ? (_jsx("button", { type: "button", className: "primary", onClick: onSubmit, disabled: !ready, title: ready ? t('Submit your answers') : t('Answer the required questions first'), children: submitLabel ?? t('Continue') })) : null] }));
|
|
125
|
+
}
|
|
126
|
+
function QuestionField(ctx) {
|
|
127
|
+
const { q, t } = ctx;
|
|
128
|
+
const renderControl = QUESTION_CONTROLS[q.type];
|
|
129
|
+
return (_jsxs("div", { className: "qf-field", children: [_jsxs("label", { className: "qf-label", children: [_jsx("span", { children: q.label }), q.required ? (_jsx("span", { className: "qf-required", "aria-label": t('required'), children: "*" })) : null] }), q.help ? _jsx("div", { className: "qf-help", children: q.help }) : null, isSecretQuestion({ question: q }) ? _jsx("div", { className: "qf-help", children: t(CREDENTIAL_CARD_GUIDANCE) }) : null, renderControl ? renderControl(ctx) : null] }));
|
|
130
|
+
}
|
|
131
|
+
function stringValue(value) {
|
|
132
|
+
return typeof value === 'string' ? value : '';
|
|
133
|
+
}
|
|
134
|
+
/** A range with no usable answer yet (e.g. mid-stream) shows its minimum, matching `emptyQuestionValue`. */
|
|
135
|
+
function rangeValue(q, value) {
|
|
136
|
+
return typeof value === 'string' && value.trim() ? value : String(q.min ?? 0);
|
|
137
|
+
}
|
|
138
|
+
function customChoice({ q, t, locked }, value, onChange) {
|
|
139
|
+
if (!shouldRenderCustomChoice(q))
|
|
140
|
+
return null;
|
|
141
|
+
return _jsx(CustomChoiceInput, { label: q.customLabel ?? t('Something else'), value: value, placeholder: q.customPlaceholder ?? t('Type your own answer'), disabled: locked, onChange: onChange });
|
|
142
|
+
}
|
|
143
|
+
function singleCustomChoice(ctx) {
|
|
144
|
+
return customChoice(ctx, customSingleValue(ctx.q, ctx.value), (next) => ctx.update(ctx.q.id, next));
|
|
145
|
+
}
|
|
146
|
+
function RadioControl(ctx) {
|
|
147
|
+
const { q, value, formId, locked, update } = ctx;
|
|
148
|
+
if (!q.options)
|
|
149
|
+
return null;
|
|
150
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "qf-options", children: q.options.map((opt) => (_jsxs("label", { className: `qf-chip${value === opt.value ? ' qf-chip-on' : ''}`, title: opt.description, children: [_jsx("input", { type: "radio", name: `${formId}-${q.id}`, value: opt.value, checked: value === opt.value, disabled: locked, "aria-label": opt.label, onChange: () => update(q.id, opt.value) }), _jsx(OptionCopy, { option: opt })] }, opt.value))) }), singleCustomChoice(ctx)] }));
|
|
151
|
+
}
|
|
152
|
+
function CheckboxControl(ctx) {
|
|
153
|
+
const { q, value, locked, toggleCheckbox, updateCheckboxCustom } = ctx;
|
|
154
|
+
if (!q.options)
|
|
155
|
+
return null;
|
|
156
|
+
const arr = Array.isArray(value) ? value : [];
|
|
157
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "qf-options", children: q.options.map((opt) => {
|
|
158
|
+
const on = arr.includes(opt.value);
|
|
159
|
+
const maxed = q.maxSelections !== undefined && !on && arr.length >= q.maxSelections;
|
|
160
|
+
return (_jsxs("label", { title: opt.description, className: `qf-chip${on ? ' qf-chip-on' : ''}${maxed ? ' qf-chip-disabled' : ''}`, children: [_jsx("input", { type: "checkbox", value: opt.value, checked: on, disabled: locked || maxed, "aria-label": opt.label, onChange: () => toggleCheckbox(q.id, opt.value, q.maxSelections) }), _jsx(OptionCopy, { option: opt })] }, opt.value));
|
|
161
|
+
}) }), customChoice(ctx, customCheckboxValue(q, value), (next) => updateCheckboxCustom(q, next))] }));
|
|
162
|
+
}
|
|
163
|
+
function SelectControl(ctx) {
|
|
164
|
+
const { q, value, locked, t, update } = ctx;
|
|
165
|
+
if (!q.options)
|
|
166
|
+
return null;
|
|
167
|
+
return (_jsxs(_Fragment, { children: [_jsxs("select", { className: "qf-select", value: typeof value === 'string' && questionValueIsKnown(q, value) ? value : '', disabled: locked, onChange: (e) => update(q.id, e.target.value), children: [_jsx("option", { value: "", disabled: true, children: q.placeholder ?? t('Choose one') }), q.options.map((opt) => (_jsx("option", { value: opt.value, title: opt.description, children: opt.label }, opt.value)))] }), singleCustomChoice(ctx)] }));
|
|
168
|
+
}
|
|
169
|
+
function DirectionCardsControl(ctx) {
|
|
170
|
+
const { q, value, formId, locked, update } = ctx;
|
|
171
|
+
if (!q.cards || q.cards.length === 0)
|
|
172
|
+
return null;
|
|
173
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "qf-direction-cards", children: q.cards.map((card) => (_jsx(DirectionCardView, { card: card, formId: formId, questionId: q.id, selected: value === card.id || value === card.label, disabled: locked, onSelect: () => update(q.id, card.id) }, card.id))) }), singleCustomChoice(ctx)] }));
|
|
174
|
+
}
|
|
175
|
+
/** date/time/datetime-local and url/email/tel share one plain input whose `type` is the question type. */
|
|
176
|
+
function TypedInputControl({ q, value, locked, update }) {
|
|
177
|
+
return _jsx("input", { type: q.type, className: "qf-input", value: stringValue(value), placeholder: q.placeholder, disabled: locked, onChange: (e) => update(q.id, e.target.value) });
|
|
178
|
+
}
|
|
179
|
+
const QUESTION_CONTROLS = {
|
|
180
|
+
radio: RadioControl,
|
|
181
|
+
checkbox: CheckboxControl,
|
|
182
|
+
select: SelectControl,
|
|
183
|
+
'direction-cards': DirectionCardsControl,
|
|
184
|
+
text: ({ q, value, locked, update }) => _jsx("input", { ...questionTextInputProps({ question: q }), className: "qf-input", value: stringValue(value), placeholder: q.placeholder, disabled: locked, onChange: (e) => update(q.id, e.target.value) }),
|
|
185
|
+
number: ({ q, value, locked, update }) => _jsx("input", { type: "number", className: "qf-input", value: stringValue(value), placeholder: q.placeholder, min: q.min, max: q.max, step: q.step, disabled: locked, onChange: (e) => update(q.id, e.target.value) }),
|
|
186
|
+
range: ({ q, value, locked, update }) => (_jsxs("div", { className: "qf-range-wrap", children: [_jsx("input", { type: "range", className: "qf-range", value: rangeValue(q, value), min: q.min, max: q.max, step: q.step, disabled: locked, onChange: (e) => update(q.id, e.target.value) }), _jsx("output", { className: "qf-range-value", children: rangeValue(q, value) })] })),
|
|
187
|
+
date: TypedInputControl,
|
|
188
|
+
time: TypedInputControl,
|
|
189
|
+
'datetime-local': TypedInputControl,
|
|
190
|
+
url: TypedInputControl,
|
|
191
|
+
email: TypedInputControl,
|
|
192
|
+
tel: TypedInputControl,
|
|
193
|
+
color: ({ q, value, locked, update }) => _jsx("input", { type: "color", className: "qf-color", value: normalizeColorInputValue(value), disabled: locked, onChange: (e) => update(q.id, e.target.value) }),
|
|
194
|
+
file: ({ q, value, locked, selectFiles }) => (_jsxs("div", { className: "qf-file-wrap", children: [_jsx("input", { type: "file", className: "qf-file", multiple: q.multiple, accept: q.accept, disabled: locked, onChange: (e) => selectFiles(q, e.target.files) }), fileValueLabel(value) ? _jsx("div", { className: "qf-file-summary", children: fileValueLabel(value) }) : null] })),
|
|
195
|
+
switch: ({ q, value, locked, update }) => (_jsxs("label", { className: "qf-switch", children: [_jsx("input", { type: "checkbox", role: "switch", checked: value === 'true', disabled: locked, onChange: (e) => update(q.id, e.target.checked ? 'true' : 'false') }), _jsx("span", { "aria-hidden": true })] })),
|
|
196
|
+
textarea: ({ q, value, locked, update }) => _jsx("textarea", { ...questionTextareaProps({ question: q }), className: "qf-textarea", value: stringValue(value), placeholder: q.placeholder, disabled: locked, rows: 3, onChange: (e) => update(q.id, e.target.value) }),
|
|
197
|
+
};
|
|
135
198
|
function OptionCopy({ option }) {
|
|
136
199
|
return (_jsxs("span", { className: "qf-chip-copy", children: [_jsx("span", { children: option.label }), option.description ? _jsx("span", { className: "qf-chip-desc", children: option.description }) : null] }));
|
|
137
200
|
}
|
|
@@ -145,22 +208,31 @@ function DirectionCardView({ card, formId, questionId, selected, disabled, onSel
|
|
|
145
208
|
}
|
|
146
209
|
function buildInitialState(form, submitted, draft) {
|
|
147
210
|
const out = {};
|
|
211
|
+
for (const q of form.questions)
|
|
212
|
+
out[q.id] = initialQuestionValue(q, submitted, draft);
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
/** Submitted beats draft beats default beats empty; file drafts are never restored (the File objects are gone). */
|
|
216
|
+
function initialQuestionValue(q, submitted, draft) {
|
|
217
|
+
if (submitted?.[q.id] !== undefined)
|
|
218
|
+
return canonicalizeQuestionValue(q, submitted[q.id]);
|
|
219
|
+
if (draft?.[q.id] !== undefined && q.type !== 'file')
|
|
220
|
+
return canonicalizeQuestionValue(q, draft[q.id]);
|
|
221
|
+
if (q.defaultValue !== undefined)
|
|
222
|
+
return canonicalizeQuestionValue(q, q.defaultValue);
|
|
223
|
+
return emptyQuestionValue(q);
|
|
224
|
+
}
|
|
225
|
+
/** Questions that stream in after mount get their initial value; answers already set are kept. */
|
|
226
|
+
function backfillAnswers(prev, form, submitted) {
|
|
227
|
+
let changed = false;
|
|
228
|
+
const next = { ...prev };
|
|
148
229
|
for (const q of form.questions) {
|
|
149
|
-
if (
|
|
150
|
-
out[q.id] = canonicalizeQuestionValue(q, submitted[q.id]);
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
if (draft && draft[q.id] !== undefined && q.type !== 'file') {
|
|
154
|
-
out[q.id] = canonicalizeQuestionValue(q, draft[q.id]);
|
|
230
|
+
if (next[q.id] !== undefined)
|
|
155
231
|
continue;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
out[q.id] = canonicalizeQuestionValue(q, q.defaultValue);
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
out[q.id] = emptyQuestionValue(q);
|
|
232
|
+
changed = true;
|
|
233
|
+
next[q.id] = initialQuestionValue(q, submitted, undefined);
|
|
162
234
|
}
|
|
163
|
-
return
|
|
235
|
+
return changed ? next : prev;
|
|
164
236
|
}
|
|
165
237
|
function draftSafeAnswers(form, answers) {
|
|
166
238
|
const fileQuestionIds = new Set(form.questions.filter((q) => q.type === 'file').map((q) => q.id));
|
|
@@ -20,7 +20,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
20
20
|
import { useRef, useState } from 'react';
|
|
21
21
|
import { useT } from '../hooks/context.js';
|
|
22
22
|
import { QuestionForm } from './QuestionForm.js';
|
|
23
|
-
export function QuestionsPanel({ form, interactive, submitDisabled
|
|
23
|
+
export function QuestionsPanel({ form, interactive, submitDisabled, submittedAnswers, generating = false, onSubmit, redactUserText, onSecretRedacted }) {
|
|
24
24
|
const t = useT();
|
|
25
25
|
const formRef = useRef(null);
|
|
26
26
|
const [ready, setReady] = useState(false);
|
|
@@ -12,18 +12,24 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
12
12
|
import { useState } from 'react';
|
|
13
13
|
import { useT } from '../hooks/context.js';
|
|
14
14
|
import { Icon } from './Icon.js';
|
|
15
|
-
export function TodoCard({ todos, runStreaming
|
|
15
|
+
export function TodoCard({ todos, runStreaming, onDismiss }) {
|
|
16
16
|
const t = useT();
|
|
17
|
-
const
|
|
18
|
-
const hasPending = todos.some((todo) => todo.status === 'pending' || todo.status === 'in_progress');
|
|
19
|
-
const defaultExpanded = todos.length > 0 && (hasInProgress || hasPending || runStreaming);
|
|
17
|
+
const { defaultExpanded, inProgressTodo, done, allComplete } = todoProgress({ todos }, { runStreaming });
|
|
20
18
|
const [overrideExpanded, setOverrideExpanded] = useState(null);
|
|
21
19
|
const expanded = overrideExpanded ?? defaultExpanded;
|
|
20
|
+
const showDismiss = Boolean(onDismiss) && allComplete;
|
|
21
|
+
return (_jsxs("div", { className: `op-card op-todo${expanded ? '' : ' op-todo-collapsed'}`, children: [_jsxs("div", { className: "op-card-head op-todo-head", children: [_jsxs("button", { type: "button", className: "op-todo-toggle", "aria-expanded": expanded, onClick: () => setOverrideExpanded(!expanded), title: expanded ? t('Collapse todos') : t('Expand todos'), children: [_jsx("span", { className: "op-icon", "aria-hidden": true, children: "\u2610" }), _jsx("span", { className: "op-title", children: t('Todos') }), _jsxs("span", { className: "op-meta", children: [done, "/", todos.length] }), !expanded && inProgressTodo ? _jsx("span", { className: "op-todo-current", children: todoLabel({ todo: inProgressTodo }) }) : null, _jsx("span", { className: "op-todo-chev", "aria-hidden": true, children: expanded ? _jsx(Icon, { name: "chevron-down", size: 11 }) : _jsx(Icon, { name: "chevron-right", size: 11 }) })] }), showDismiss ? (_jsx("button", { type: "button", className: "op-todo-done", onClick: () => onDismiss?.(), title: t('Dismiss'), children: t('Done') })) : null] }), _jsx("div", { className: `accordion-collapsible${expanded ? ' open' : ''}`, children: _jsx("div", { className: "accordion-collapsible-inner", children: _jsx("ul", { className: "todo-list", children: todos.map((todo, i) => (_jsxs("li", { className: `todo-item todo-${todo.status}`, children: [_jsx("span", { className: "todo-check", "aria-hidden": true, children: todo.status === 'completed' ? '✓' : todo.status === 'in_progress' ? '◐' : todo.status === 'stopped' ? '!' : '○' }), _jsx("span", { className: "todo-text", children: todoLabel({ todo }) })] }, i))) }) }) })] }));
|
|
22
|
+
}
|
|
23
|
+
function todoProgress({ todos }, { runStreaming = false } = {}) {
|
|
24
|
+
const hasPending = todos.some((todo) => todo.status === 'pending' || todo.status === 'in_progress');
|
|
25
|
+
const defaultExpanded = todos.length > 0 && (hasPending || runStreaming);
|
|
22
26
|
const inProgressTodo = todos.find((todo) => todo.status === 'in_progress');
|
|
23
27
|
const completed = todos.filter((todo) => todo.status === 'completed').length;
|
|
24
28
|
const done = todos.filter((todo) => todo.status === 'completed' || todo.status === 'in_progress').length;
|
|
25
29
|
const allComplete = todos.length > 0 && completed === todos.length;
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
return { defaultExpanded, inProgressTodo, done, allComplete };
|
|
31
|
+
}
|
|
32
|
+
function todoLabel({ todo }, _options = {}) {
|
|
33
|
+
return todo.status === 'in_progress' && todo.activeForm ? todo.activeForm : todo.content;
|
|
28
34
|
}
|
|
29
35
|
//# sourceMappingURL=TodoCard.js.map
|
|
@@ -2,11 +2,12 @@ import type { ReactNode } from 'react';
|
|
|
2
2
|
import type { ToolDisplayRow } from '../tool-call-display.js';
|
|
3
3
|
/**
|
|
4
4
|
* One turn's tool cards folded into a single line ("Used 3 tools ▸") that opens on click — the
|
|
5
|
-
* `toolCallDisplay: 'collapsed'` layout
|
|
6
|
-
* `useToolCallGroup
|
|
5
|
+
* `toolCallDisplay: 'collapsed'` layout, or the compact failure notice in 'hidden'. The disclosure
|
|
6
|
+
* state lives in `useToolCallGroup`; copy uses the host's translator.
|
|
7
7
|
*/
|
|
8
|
-
export declare function ToolCallGroup<Row extends ToolDisplayRow>({ rows, renderRow }: {
|
|
8
|
+
export declare function ToolCallGroup<Row extends ToolDisplayRow>({ rows, renderRow, failureOnly }: {
|
|
9
9
|
rows: readonly Row[];
|
|
10
|
+
failureOnly?: boolean;
|
|
10
11
|
renderRow: (row: Row) => ReactNode;
|
|
11
12
|
}): import("react").JSX.Element;
|
|
12
13
|
//# sourceMappingURL=ToolCallGroup.d.ts.map
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useToolCallGroup } from './ToolCallGroup.hooks.js';
|
|
3
|
+
import { useT } from '../hooks/context.js';
|
|
3
4
|
import { Icon } from './Icon.js';
|
|
4
5
|
/**
|
|
5
6
|
* One turn's tool cards folded into a single line ("Used 3 tools ▸") that opens on click — the
|
|
6
|
-
* `toolCallDisplay: 'collapsed'` layout
|
|
7
|
-
* `useToolCallGroup
|
|
7
|
+
* `toolCallDisplay: 'collapsed'` layout, or the compact failure notice in 'hidden'. The disclosure
|
|
8
|
+
* state lives in `useToolCallGroup`; copy uses the host's translator.
|
|
8
9
|
*/
|
|
9
|
-
export function ToolCallGroup({ rows, renderRow }) {
|
|
10
|
+
export function ToolCallGroup({ rows, renderRow, failureOnly = false }) {
|
|
10
11
|
const group = useToolCallGroup({ rows });
|
|
11
|
-
|
|
12
|
+
const t = useT();
|
|
13
|
+
return (_jsxs("div", { className: "jini-tool-group", "data-failed": group.failed ? 'true' : undefined, children: [failureOnly ? _jsxs("span", { className: "jini-tool-group__label", children: [t('A step failed'), " "] }) : null, _jsxs("button", { type: "button", className: "jini-tool-group__toggle", "aria-expanded": group.open, onClick: group.toggle, children: [_jsx("span", { className: `jini-tool-group__label${group.running ? ' shimmer-text' : ''}`, children: failureOnly ? t('Show') : group.label }), _jsx(Icon, { name: group.open ? 'chevron-down' : 'chevron-right', size: 12 })] }), group.open ? _jsx("div", { className: "jini-message-tools", children: rows.map(renderRow) }) : null] }));
|
|
12
14
|
}
|
|
13
15
|
//# sourceMappingURL=ToolCallGroup.js.map
|
|
@@ -27,14 +27,11 @@ import { Icon } from './Icon.js';
|
|
|
27
27
|
import { TodoCard } from './TodoCard.js';
|
|
28
28
|
/** 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
29
|
const JINI_TOOL_ID_PATTERN = /^[a-z][a-z_]*(\.[a-z][a-z_]*)+$/;
|
|
30
|
-
const SEARCH_TOOLS_NAMES = new Set(['search_tools', 'mcp__jini__search_tools']);
|
|
31
|
-
const DESCRIBE_TOOL_NAMES = new Set(['describe_tool', 'mcp__jini__describe_tool']);
|
|
32
30
|
export function ToolCard({ use, result, runStreaming, runSucceeded, projectFileNames, onRequestOpenFile }) {
|
|
33
31
|
const registry = useToolRendererRegistry();
|
|
34
32
|
const name = use.name;
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const content = renderToolCardBody(name, use, result, isStreaming, isSucceeded, { projectFileNames, onRequestOpenFile }, registry);
|
|
33
|
+
const card = { input: use.input, result: result, runStreaming: runStreaming ?? false, runSucceeded: runSucceeded ?? false };
|
|
34
|
+
const content = renderToolCardBody({ use, card, fileCtx: { projectFileNames, onRequestOpenFile }, registry });
|
|
38
35
|
if (content === null)
|
|
39
36
|
return null;
|
|
40
37
|
// Tagged once here, at the single dispatch point every branch below funnels through, rather than
|
|
@@ -44,18 +41,42 @@ export function ToolCard({ use, result, runStreaming, runSucceeded, projectFileN
|
|
|
44
41
|
// ambiguous the way a repeated literal handle would be.
|
|
45
42
|
return (_jsx("div", { "data-agent-element": `tool-call-${use.id}`, "data-agent-role": "region", "data-agent-label": `A ${humanizeToolId(name)} tool call the AI made`, children: content }));
|
|
46
43
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
// Hardcoded family cards for tools shipped with this package, keyed by every name each tool goes
|
|
45
|
+
// by. A `Map`, not an object literal, so a tool named `constructor`/`toString` can never resolve
|
|
46
|
+
// to an inherited `Object.prototype` member.
|
|
47
|
+
const FAMILY_CARDS = new Map([
|
|
48
|
+
[['Write', 'write', 'create_file'], (card, fileCtx) => _jsx(FileWriteCard, { ...card, ctx: fileCtx })],
|
|
49
|
+
[['Edit', 'str_replace_edit'], (card, fileCtx) => _jsx(FileEditCard, { ...card, ctx: fileCtx })],
|
|
50
|
+
[['Read', 'read_file'], (card, fileCtx) => _jsx(FileReadCard, { ...card, ctx: fileCtx })],
|
|
51
|
+
[['Bash'], (card) => _jsx(BashCard, { ...card })],
|
|
52
|
+
[['Glob', 'list_files'], (card) => _jsx(GlobCard, { ...card })],
|
|
53
|
+
[['Grep'], (card) => _jsx(GrepCard, { ...card })],
|
|
54
|
+
[['WebFetch', 'web_fetch'], (card) => _jsx(WebFetchCard, { ...card })],
|
|
55
|
+
[['WebSearch', 'web_search'], (card) => _jsx(WebSearchCard, { ...card })],
|
|
56
|
+
[['search_tools', 'mcp__jini__search_tools'], (card) => _jsx(SearchToolsCard, { ...card })],
|
|
57
|
+
[['describe_tool', 'mcp__jini__describe_tool'], (card) => _jsx(DescribeToolCard, { ...card })],
|
|
58
|
+
].flatMap(([names, render]) => names.map((name) => [name, render])));
|
|
59
|
+
/** A host-registered renderer's output, or `undefined` when none is registered, it renders nothing, or it throws (logged) — every one of those falls back to the built-in cards. */
|
|
60
|
+
function renderRegistered({ use, card, registry }) {
|
|
61
|
+
const custom = registry.get({ name: use.name });
|
|
62
|
+
if (!custom)
|
|
63
|
+
return undefined;
|
|
64
|
+
try {
|
|
65
|
+
const node = custom(toRenderProps({ use: use, result: card.result, runStreaming: card.runStreaming }, { runSucceeded: card.runSucceeded }));
|
|
66
|
+
if (node !== undefined && node !== null && node !== false)
|
|
67
|
+
return node;
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
console.error(`[ToolCard] custom renderer for "${use.name}" threw; falling back`, err);
|
|
58
71
|
}
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
function renderToolCardBody(args) {
|
|
75
|
+
const custom = renderRegistered(args);
|
|
76
|
+
if (custom !== undefined)
|
|
77
|
+
return custom;
|
|
78
|
+
const { use, card, fileCtx } = args;
|
|
79
|
+
const name = use.name;
|
|
59
80
|
// `execute_delegated_tool` is a transport wrapper: `@jini-ai/daemon`'s `delegated-tool-bridge.ts`
|
|
60
81
|
// always emits a second, canonical `tool_use` named after the real Jini tool id (`page.fill`,
|
|
61
82
|
// not `execute_delegated_tool`) alongside it — before calling `ToolExecutor`, so it exists even
|
|
@@ -68,37 +89,23 @@ function renderToolCardBody(name, use, result, isStreaming, isSucceeded, fileCtx
|
|
|
68
89
|
// `useToolTimeline`: `foldDelegatedWrapperCalls` drops it in favour of that canonical row. This
|
|
69
90
|
// check still covers a host that renders `<ToolCard>` straight from raw events.)
|
|
70
91
|
// Covers the read-only gateway (`execute_readonly_delegated_tool`) too.
|
|
71
|
-
if (isDelegatedWrapperToolName({ name: name }) && result && !result.isError)
|
|
92
|
+
if (isDelegatedWrapperToolName({ name: name }) && card.result && !card.result.isError)
|
|
72
93
|
return null;
|
|
73
94
|
if (isTodoWriteToolName({ name: name }))
|
|
74
|
-
return _jsx(TodoCard, { todos: parseTodoWriteInput({ input: use.input }), runStreaming:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
return
|
|
81
|
-
if (name === 'Bash')
|
|
82
|
-
return _jsx(BashCard, { input: use.input, result: result, runStreaming: isStreaming, runSucceeded: isSucceeded });
|
|
83
|
-
if (name === 'Glob' || name === 'list_files')
|
|
84
|
-
return _jsx(GlobCard, { input: use.input, result: result, runStreaming: isStreaming, runSucceeded: isSucceeded });
|
|
85
|
-
if (name === 'Grep')
|
|
86
|
-
return _jsx(GrepCard, { input: use.input, result: result, runStreaming: isStreaming, runSucceeded: isSucceeded });
|
|
87
|
-
if (name === 'WebFetch' || name === 'web_fetch')
|
|
88
|
-
return _jsx(WebFetchCard, { input: use.input, result: result, runStreaming: isStreaming, runSucceeded: isSucceeded });
|
|
89
|
-
if (name === 'WebSearch' || name === 'web_search')
|
|
90
|
-
return _jsx(WebSearchCard, { input: use.input, result: result, runStreaming: isStreaming, runSucceeded: isSucceeded });
|
|
95
|
+
return _jsx(TodoCard, { todos: parseTodoWriteInput({ input: use.input }), runStreaming: card.runStreaming });
|
|
96
|
+
// Jini tool ids are dotted and wrapper names are `execute_*delegated_tool`, so neither ever
|
|
97
|
+
// collides with a family name — the search/describe cards can sit in the same table as the rest
|
|
98
|
+
// even though they are looked up ahead of the Jini-id check below.
|
|
99
|
+
const family = FAMILY_CARDS.get(name);
|
|
100
|
+
if (family)
|
|
101
|
+
return family(card, fileCtx);
|
|
91
102
|
if (JINI_TOOL_ID_PATTERN.test(name) || isDelegatedWrapperToolName({ name: name })) {
|
|
92
103
|
// The unsuppressed (failed-wrapper) case falls through here too: `name` is still
|
|
93
104
|
// `execute_delegated_tool`, so `jiniToolIdFromInput` recovers the real id from
|
|
94
105
|
// `{toolId, input}` instead of `DelegatedToolCard` receiving its own wrapper name as the title.
|
|
95
|
-
return _jsx(DelegatedToolCard, { name: name,
|
|
106
|
+
return _jsx(DelegatedToolCard, { name: name, ...card });
|
|
96
107
|
}
|
|
97
|
-
|
|
98
|
-
return _jsx(SearchToolsCard, { input: use.input, result: result, runStreaming: isStreaming, runSucceeded: isSucceeded });
|
|
99
|
-
if (DESCRIBE_TOOL_NAMES.has(name))
|
|
100
|
-
return _jsx(DescribeToolCard, { input: use.input, result: result, runStreaming: isStreaming, runSucceeded: isSucceeded });
|
|
101
|
-
return _jsx(GenericCard, { name: name, input: use.input, result: result, runStreaming: isStreaming, runSucceeded: isSucceeded });
|
|
108
|
+
return _jsx(GenericCard, { name: name, ...card });
|
|
102
109
|
}
|
|
103
110
|
/** `"page.fill"` -> `"Page Fill"`, `"daemon.db.vacuum"` -> `"Daemon Db Vacuum"`. Cosmetic only — never used to make a routing decision. */
|
|
104
111
|
function humanizeToolId(id) {
|
|
@@ -9,8 +9,13 @@ export function parseComposerSlashQuery({ draft }) {
|
|
|
9
9
|
const match = SLASH_QUERY_RE.exec(draft);
|
|
10
10
|
if (!match)
|
|
11
11
|
return null;
|
|
12
|
+
// The `as string` casts are guaranteed by SLASH_QUERY_RE itself, not assumed: group 1 is a bare
|
|
13
|
+
// `*` capture outside any optional group, so it always participates (possibly as ''); group 3 is
|
|
14
|
+
// `*` inside the same optional group as group 2, so it participates whenever a separator does.
|
|
15
|
+
// `noUncheckedIndexedAccess` cannot see either guarantee, and the `?? ''` fallbacks that used to
|
|
16
|
+
// satisfy it were unreachable branches.
|
|
12
17
|
const [, command, separator, rest] = match;
|
|
13
|
-
return { command: command
|
|
18
|
+
return { command: command, argument: separator === undefined ? null : rest };
|
|
14
19
|
}
|
|
15
20
|
/**
|
|
16
21
|
* Once a separator has been typed, only an item whose declared `command` exactly equals the typed
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module components/mcp-ui-surface-state
|
|
3
|
-
*
|
|
4
|
-
* Whether an MCP-UI card in a chat transcript is closed, and with which label. Kept out of
|
|
5
|
-
* `McpUiSurfaceCard.tsx` so the component only renders; moved here from that file 2026-10-04 when
|
|
6
|
-
* the card's own deadline (`useSurfaceExpiry`) became a second reason to close it.
|
|
7
|
-
*/
|
|
8
1
|
import type { McpUiToolCallHandler } from '@jini-ai/ui/mcp-ui';
|
|
9
2
|
import type { ExtEventCall } from '../ext-event-renderer-registry.js';
|
|
10
3
|
import type { useT } from '../hooks/context.js';
|
|
@@ -35,14 +28,16 @@ export declare function closedSurfaceLabel({ t, call, runStreaming, documentsFor
|
|
|
35
28
|
export declare function isDeliveredAnswer(result: unknown): boolean;
|
|
36
29
|
/**
|
|
37
30
|
* Wraps a card's tool executor so a delivered answer is reported once it resolves. The result is
|
|
38
|
-
* passed through unchanged
|
|
39
|
-
* be answered again).
|
|
31
|
+
* passed through unchanged; onAnswerText receives readable choice text when available. A rejected call
|
|
32
|
+
* reports nothing (the dialog shows its error and can be answered again).
|
|
40
33
|
*
|
|
41
|
-
* @complexity O(
|
|
34
|
+
* @complexity O(p + s*m) answer-label mapping, plus `onToolCall`'s own cost (see questionAnswerText).
|
|
42
35
|
*/
|
|
43
|
-
export declare function reportDeliveredAnswer({ onToolCall, onAnswered }: {
|
|
36
|
+
export declare function reportDeliveredAnswer({ onToolCall, onAnswered, onAnswerText, input }: {
|
|
44
37
|
onToolCall: McpUiToolCallHandler;
|
|
45
38
|
onAnswered: () => void;
|
|
39
|
+
onAnswerText?: ((answerText: string) => void) | undefined;
|
|
40
|
+
input?: unknown;
|
|
46
41
|
}): McpUiToolCallHandler;
|
|
47
42
|
export {};
|
|
48
43
|
//# sourceMappingURL=mcp-ui-surface-state.d.ts.map
|