@jini-ai/ui 0.6.22 → 0.6.23
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 +4 -0
- package/dist/features/mcp-ui/secret-card.js +4 -1
- package/dist/features/mcp-ui/surfaces/confirmation.js +1 -1
- package/dist/features/mcp-ui/surfaces/document.d.ts +15 -5
- package/dist/features/mcp-ui/surfaces/document.js +17 -7
- package/dist/react/components/SecretInput.d.ts +3 -2
- package/dist/react/components/SecretInput.js +6 -3
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -15,7 +15,7 @@ import { buildOutcomeSurface } from './surfaces/outcome.js';
|
|
|
15
15
|
/** Reject ambiguous or protocol-reserved names and runtime attempts to prefill secrets.
|
|
16
16
|
* @complexity O(n) time/space in the field count. Throws fixed ToolInputError text only. */
|
|
17
17
|
function checkedFields(form) {
|
|
18
|
-
const names = new Set([SURFACE_EXCHANGE_ID_PARAM, SURFACE_DISMISSED_PARAM, SURFACE_TYPED_ANSWER_PARAM]);
|
|
18
|
+
const names = new Set([SURFACE_EXCHANGE_ID_PARAM, SURFACE_DISMISSED_PARAM, SURFACE_TYPED_ANSWER_PARAM, 'freeText']);
|
|
19
19
|
for (const field of form.fields) {
|
|
20
20
|
if (!field.name || names.has(field.name))
|
|
21
21
|
throw new ToolInputError({ message: 'Secret card field names must be unique and cannot use exchange parameters.' });
|
|
@@ -36,6 +36,9 @@ function answerStep(required) {
|
|
|
36
36
|
return { run: { status: answer.status } };
|
|
37
37
|
if (answer.params[SURFACE_DISMISSED_PARAM] === true)
|
|
38
38
|
return { run: { status: 'cancelled' } };
|
|
39
|
+
// Composer answers are displayed as chat text, never accepted as secret form submissions.
|
|
40
|
+
if (Object.hasOwn(answer.params, SURFACE_TYPED_ANSWER_PARAM))
|
|
41
|
+
return { run: { status: 'cancelled' } };
|
|
39
42
|
return { params: answer.params };
|
|
40
43
|
}
|
|
41
44
|
/** The exact submitted secret, or undefined when it is blank or malformed.
|
|
@@ -137,7 +137,7 @@ function confirmationBodyHtml(spec, required) {
|
|
|
137
137
|
renderChoices(spec.choices ?? []),
|
|
138
138
|
warning,
|
|
139
139
|
// Confirm starts disabled; the script enables it once the dwell has run out.
|
|
140
|
-
renderActions(confirmationActions(spec).map((action) => (affirmative.has(action.id) ? { ...action, disabled: true } : action))),
|
|
140
|
+
renderActions(confirmationActions(spec).map((action) => (affirmative.has(action.id) ? { ...action, disabled: true } : action)), { humanOnly: true }),
|
|
141
141
|
renderStatusRegion(),
|
|
142
142
|
]
|
|
143
143
|
.filter((fragment) => fragment !== '')
|
|
@@ -179,16 +179,26 @@ export interface SurfaceAction {
|
|
|
179
179
|
* `script-src 'unsafe-inline'`-equivalent allowance for attributes specifically, and a host is free
|
|
180
180
|
* to apply a stricter CSP to the frame than this package can see.
|
|
181
181
|
*
|
|
182
|
-
*
|
|
182
|
+
* Ordinary buttons also carry the `@jini-ai/agentic` `data-agent-*` markup convention
|
|
183
183
|
* ({@link AGENT_ELEMENT_ATTRIBUTE} et al.) — but this tagging is advisory, not the thing that makes
|
|
184
184
|
* a surface's actions reachable by `page.find_elements`/`page.click`. Those capabilities resolve a
|
|
185
185
|
* handle by scanning `document`/`contentDocument`, and this document renders inside a `srcdoc`
|
|
186
186
|
* iframe sandboxed to `allow-scripts` alone (`MCP_UI_VIEW_SANDBOX` — no `allow-same-origin`,
|
|
187
187
|
* deliberately), which gives it an opaque origin no ancestor document can read into. So this tagging
|
|
188
188
|
* is real for a driver that reaches INTO the frame directly — `frameLocator` in Playwright, proven
|
|
189
|
-
* end to end against exactly this markup — and inert for anything scanning the parent page. Making
|
|
190
|
-
* surface's actions visible to `page.find_elements` needs a second, parent-DOM-side echo
|
|
191
|
-
*
|
|
189
|
+
* end to end against exactly this markup — and inert for anything scanning the parent page. Making an
|
|
190
|
+
* ordinary surface's actions visible to `page.find_elements` needs a second, parent-DOM-side echo
|
|
191
|
+
* of that action list. Human-only decisions are intentionally excluded from addressing even
|
|
192
|
+
* inside the frame: confirmation buttons are private and their runtime still requires trusted clicks.
|
|
193
|
+
*
|
|
194
|
+
* @param actions - Action descriptions; the legacy array argument is retained for caller compatibility.
|
|
195
|
+
* @param options - Human-only rows omit agent addressing and mark every decision private.
|
|
196
|
+
* @returns Escaped HTML without executing actions or changing their runtime click gates.
|
|
197
|
+
* @complexity O(n) time and space in the emitted markup length.
|
|
198
|
+
* @overallScore 98/100
|
|
199
|
+
* @qualityFindings Low: The legacy array-first signature remains a compatibility exception to object arguments.
|
|
192
200
|
*/
|
|
193
|
-
export declare function renderActions(actions: readonly SurfaceAction[]
|
|
201
|
+
export declare function renderActions(actions: readonly SurfaceAction[], { humanOnly }?: {
|
|
202
|
+
humanOnly?: boolean;
|
|
203
|
+
}): string;
|
|
194
204
|
//# sourceMappingURL=document.d.ts.map
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* builders because the CSS that styles them lives here — splitting the markup from the only
|
|
12
12
|
* stylesheet that can reach it is how a "generic" fragment quietly becomes unstyled in one caller.
|
|
13
13
|
*/
|
|
14
|
-
import { AGENT_ELEMENT_ATTRIBUTE, AGENT_LABEL_ATTRIBUTE, AGENT_ROLE_ATTRIBUTE } from '@jini-ai/agentic';
|
|
14
|
+
import { AGENT_ELEMENT_ATTRIBUTE, AGENT_LABEL_ATTRIBUTE, AGENT_PRIVATE_ATTRIBUTE, AGENT_ROLE_ATTRIBUTE } from '@jini-ai/agentic';
|
|
15
15
|
import { escapeHtml } from '../escape.js';
|
|
16
16
|
import { SURFACE_BRIDGE_GLOBAL, renderBridgeScript } from './bridge.js';
|
|
17
17
|
import { renderTokenBlock } from './tokens.js';
|
|
@@ -429,18 +429,26 @@ export function fieldDescribedBy(spec) {
|
|
|
429
429
|
* `script-src 'unsafe-inline'`-equivalent allowance for attributes specifically, and a host is free
|
|
430
430
|
* to apply a stricter CSP to the frame than this package can see.
|
|
431
431
|
*
|
|
432
|
-
*
|
|
432
|
+
* Ordinary buttons also carry the `@jini-ai/agentic` `data-agent-*` markup convention
|
|
433
433
|
* ({@link AGENT_ELEMENT_ATTRIBUTE} et al.) — but this tagging is advisory, not the thing that makes
|
|
434
434
|
* a surface's actions reachable by `page.find_elements`/`page.click`. Those capabilities resolve a
|
|
435
435
|
* handle by scanning `document`/`contentDocument`, and this document renders inside a `srcdoc`
|
|
436
436
|
* iframe sandboxed to `allow-scripts` alone (`MCP_UI_VIEW_SANDBOX` — no `allow-same-origin`,
|
|
437
437
|
* deliberately), which gives it an opaque origin no ancestor document can read into. So this tagging
|
|
438
438
|
* is real for a driver that reaches INTO the frame directly — `frameLocator` in Playwright, proven
|
|
439
|
-
* end to end against exactly this markup — and inert for anything scanning the parent page. Making
|
|
440
|
-
* surface's actions visible to `page.find_elements` needs a second, parent-DOM-side echo
|
|
441
|
-
*
|
|
439
|
+
* end to end against exactly this markup — and inert for anything scanning the parent page. Making an
|
|
440
|
+
* ordinary surface's actions visible to `page.find_elements` needs a second, parent-DOM-side echo
|
|
441
|
+
* of that action list. Human-only decisions are intentionally excluded from addressing even
|
|
442
|
+
* inside the frame: confirmation buttons are private and their runtime still requires trusted clicks.
|
|
443
|
+
*
|
|
444
|
+
* @param actions - Action descriptions; the legacy array argument is retained for caller compatibility.
|
|
445
|
+
* @param options - Human-only rows omit agent addressing and mark every decision private.
|
|
446
|
+
* @returns Escaped HTML without executing actions or changing their runtime click gates.
|
|
447
|
+
* @complexity O(n) time and space in the emitted markup length.
|
|
448
|
+
* @overallScore 98/100
|
|
449
|
+
* @qualityFindings Low: The legacy array-first signature remains a compatibility exception to object arguments.
|
|
442
450
|
*/
|
|
443
|
-
export function renderActions(actions) {
|
|
451
|
+
export function renderActions(actions, { humanOnly = false } = {}) {
|
|
444
452
|
const buttons = actions
|
|
445
453
|
.map((action) => {
|
|
446
454
|
const variant = action.variant ?? 'neutral';
|
|
@@ -454,7 +462,9 @@ export function renderActions(actions) {
|
|
|
454
462
|
// would accept. That is unenforced on purpose (see the function doc above: `page.find_elements`
|
|
455
463
|
// can never reach this document to resolve it either way), so failing loudly here would refuse
|
|
456
464
|
// markup for a reason that can never matter to the one consumer that can actually query it.
|
|
457
|
-
|
|
465
|
+
const addressing = humanOnly ? AGENT_PRIVATE_ATTRIBUTE
|
|
466
|
+
: `${AGENT_ELEMENT_ATTRIBUTE}="mcpui-action-${escapeHtml(action.id)}" ${AGENT_ROLE_ATTRIBUTE}="button" ${AGENT_LABEL_ATTRIBUTE}="${label}"`;
|
|
467
|
+
return ` <button type="${action.type ?? 'button'}" class="mcpui-button${variantClass}" data-mcpui-action="${escapeHtml(action.id)}" ${addressing}${action.disabled === true ? ' disabled' : ''}>${label}</button>`;
|
|
458
468
|
})
|
|
459
469
|
.join('\n');
|
|
460
470
|
return `<div class="mcpui-actions">\n${buttons}\n</div>`;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { InputHTMLAttributes, TextareaHTMLAttributes } from 'react';
|
|
2
2
|
/**
|
|
3
3
|
* @file The shared masked input: a password/token/key `<input>` with an eye toggle inside its right
|
|
4
|
-
* edge. Drop-in for `<input type="password">` —
|
|
4
|
+
* edge. Drop-in for `<input type="password">` — ordinary input props pass through; agent
|
|
5
|
+
* addressing is excluded and privacy remains enforced after reveal.
|
|
5
6
|
*
|
|
6
7
|
* Same markup and classes as `@jini-ai/ui-kit`'s password `TextField` and the mcp-ui secret fields
|
|
7
8
|
* (`.jini-secret` / `.jini-secret-toggle`), so one host stylesheet styles all of them; hosts own
|
|
@@ -25,7 +26,7 @@ interface SecretToggleProps {
|
|
|
25
26
|
readonly show: string;
|
|
26
27
|
readonly hide: string;
|
|
27
28
|
};
|
|
28
|
-
/** Extra attributes for the toggle button
|
|
29
|
+
/** Extra ordinary attributes for the toggle button; agent addressing is always excluded. */
|
|
29
30
|
readonly toggleAttrs?: Readonly<Record<string, string | boolean | undefined>>;
|
|
30
31
|
/** Controlled mode: the host owns the shown/hidden state. */
|
|
31
32
|
readonly revealed?: boolean;
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef } from 'react';
|
|
3
3
|
import { SecretInput as KitSecretInput } from '@jini-ai/ui-kit/react';
|
|
4
|
+
import { secretControlAttrs } from '@jini-ai/ui-kit';
|
|
4
5
|
import { useT } from '../../features/i18n/index.js';
|
|
5
6
|
import { Icon } from './Icon.js';
|
|
6
7
|
import { useSecretReveal } from './SecretInput.hooks.js';
|
|
7
|
-
/** Reveal state + the eye button, shared by `SecretInput` and `SecretTextarea`.
|
|
8
|
+
/** Reveal state + the private eye button, shared by `SecretInput` and `SecretTextarea`.
|
|
9
|
+
* @complexity O(p) time and space in toggle props; no I/O.
|
|
10
|
+
* @overallScore 100 */
|
|
8
11
|
function useSecretToggle({ label, toggleLabels, toggleAttrs, revealed, onRevealedChange }, { value, disabled } = {}) {
|
|
9
12
|
const t = useT();
|
|
10
13
|
const reveal = useSecretReveal({ value, labels: toggleLabels ?? { show: `${t('Show')} ${label}`, hide: `${t('Hide')} ${label}` } }, { ...(revealed === undefined ? {} : { revealed }), ...(onRevealedChange ? { onRevealedChange } : {}) });
|
|
11
14
|
// One signal only: the name flips Show/Hide, so no `aria-pressed` — both together made screen
|
|
12
15
|
// readers announce "Hide API key, toggle button, pressed" (Opus UI review 2026-10-26).
|
|
13
|
-
const button = (_jsx("button", { ...toggleAttrs, type: "button", className: "jini-secret-toggle", "aria-label": reveal.toggleLabel, title: reveal.toggleLabel, disabled: disabled, onClick: reveal.toggle, children: _jsx(Icon, { name: reveal.revealed ? 'eye-off' : 'eye', size: 18 }) }));
|
|
16
|
+
const button = (_jsx("button", { ...secretControlAttrs({ attrs: toggleAttrs ?? {} }), type: "button", className: "jini-secret-toggle", "aria-label": reveal.toggleLabel, title: reveal.toggleLabel, disabled: disabled, onClick: reveal.toggle, children: _jsx(Icon, { name: reveal.revealed ? 'eye-off' : 'eye', size: 18 }) }));
|
|
14
17
|
return { reveal, button };
|
|
15
18
|
}
|
|
16
19
|
export const SecretInput = forwardRef(function SecretInput(props, ref) {
|
|
@@ -26,6 +29,6 @@ export const SecretTextarea = forwardRef(function SecretTextarea(props, ref) {
|
|
|
26
29
|
/* Not a standard CSS property, so it is set here rather than left to a host stylesheet: a host
|
|
27
30
|
that forgot it would show the secret in clear text. pre-wrap keeps line breaks while masked. */
|
|
28
31
|
const masked = { ...style, ...{ WebkitTextSecurity: reveal.revealed || replacementShown ? 'none' : 'disc' }, whiteSpace: 'pre-wrap' };
|
|
29
|
-
return (_jsxs("span", { className: "jini-secret jini-secret--multiline", children: [_jsx("textarea", { ...textareaProps, ...(replacementShown ? { value: maskedValue, readOnly: true } : {}), spellCheck: false, ref: ref, style: masked }), button] }));
|
|
32
|
+
return (_jsxs("span", { className: "jini-secret jini-secret--multiline", children: [_jsx("textarea", { ...secretControlAttrs({ attrs: textareaProps }), ...(replacementShown ? { value: maskedValue, readOnly: true } : {}), spellCheck: false, ref: ref, style: masked }), button] }));
|
|
30
33
|
});
|
|
31
34
|
//# sourceMappingURL=SecretInput.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/ui",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.23",
|
|
4
4
|
"description": "Generic, product-neutral React UI primitives and feature domains. Framework-free types, decision rules, catalogs, and host port contracts live behind ./core (no React, no DOM). The Excalidraw and Lexical editors live behind ./sketch-editor and ./lexical-rich-text-editor; the GrapesJS-backed inline HTML editor lives behind ./html-editor.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -263,19 +263,19 @@
|
|
|
263
263
|
"shiki": "^1.24.4",
|
|
264
264
|
"tailwind-merge": "^2.6.0",
|
|
265
265
|
"zod": "^3.25.76",
|
|
266
|
-
"@jini-ai/
|
|
267
|
-
"@jini-ai/
|
|
266
|
+
"@jini-ai/agent-runtime": "^0.6.23",
|
|
267
|
+
"@jini-ai/agentic": "^0.6.23",
|
|
268
268
|
"@jini-ai/integrations": "^0.6.19",
|
|
269
|
-
"@jini-ai/
|
|
270
|
-
"@jini-ai/
|
|
269
|
+
"@jini-ai/core": "^0.6.23",
|
|
270
|
+
"@jini-ai/platform": "^0.6.23"
|
|
271
271
|
},
|
|
272
272
|
"dependencies": {
|
|
273
273
|
"micromark": "^4.0.0",
|
|
274
274
|
"micromark-extension-gfm": "^3.0.0",
|
|
275
|
-
"@jini-ai/
|
|
276
|
-
"@jini-ai/
|
|
277
|
-
"@jini-ai/
|
|
278
|
-
"@jini-ai/
|
|
275
|
+
"@jini-ai/ui-kit": "^0.6.23",
|
|
276
|
+
"@jini-ai/core": "^0.6.23",
|
|
277
|
+
"@jini-ai/platform": "^0.6.23",
|
|
278
|
+
"@jini-ai/agentic": "^0.6.23"
|
|
279
279
|
},
|
|
280
280
|
"peerDependencies": {
|
|
281
281
|
"@tanstack/react-query": "^5.0.0",
|