@happyvertical/smrt-svelte 0.43.3 → 0.43.5
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/AGENTS.md +45 -0
- package/README.md +73 -0
- package/dist/Provider.svelte +83 -26
- package/dist/Provider.svelte.d.ts +4 -12
- package/dist/Provider.svelte.d.ts.map +1 -1
- package/dist/__tests__/provider-webmcp-harness.svelte +14 -0
- package/dist/__tests__/provider-webmcp-harness.svelte.d.ts +8 -0
- package/dist/__tests__/provider-webmcp-harness.svelte.d.ts.map +1 -0
- package/dist/components/forms/AddressInput.svelte +155 -42
- package/dist/components/forms/AddressInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/CheckboxInput.svelte +40 -13
- package/dist/components/forms/CheckboxInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/DateRangeInput.svelte +132 -16
- package/dist/components/forms/DateRangeInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/DateTimeInput.svelte +48 -14
- package/dist/components/forms/DateTimeInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/Form.svelte +667 -61
- package/dist/components/forms/Form.svelte.d.ts +6 -2
- package/dist/components/forms/Form.svelte.d.ts.map +1 -1
- package/dist/components/forms/MeasurementInput.svelte +137 -31
- package/dist/components/forms/MeasurementInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/MoneyInput.svelte +42 -23
- package/dist/components/forms/MoneyInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/NumberInput.svelte +59 -27
- package/dist/components/forms/NumberInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/PhoneInput.svelte +51 -22
- package/dist/components/forms/PhoneInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/SelectInput.svelte +38 -22
- package/dist/components/forms/SelectInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/TextInput.svelte +32 -18
- package/dist/components/forms/TextInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/TextareaInput.svelte +25 -18
- package/dist/components/forms/TextareaInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/__tests__/AddressInput.behavior.test.js +94 -0
- package/dist/components/forms/__tests__/Form.behavior.test.js +1 -1
- package/dist/components/forms/__tests__/Form.extraction.test.js +68 -0
- package/dist/components/forms/__tests__/Form.test.js +28 -0
- package/dist/components/forms/__tests__/Form.webmcp.test.js +2120 -111
- package/dist/components/forms/__tests__/FormMicButton.test.js +0 -1
- package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte +30 -0
- package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte.d.ts +7 -0
- package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/async-validation-form.fixture.svelte +19 -0
- package/dist/components/forms/__tests__/async-validation-form.fixture.svelte.d.ts +9 -0
- package/dist/components/forms/__tests__/async-validation-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte +21 -0
- package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte.d.ts +8 -0
- package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte +27 -0
- package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte.d.ts +9 -0
- package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte +48 -0
- package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte.d.ts +10 -0
- package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte +26 -0
- package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte.d.ts +13 -0
- package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte +52 -0
- package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte.d.ts +16 -0
- package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-with-fields.fixture.svelte +108 -5
- package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts +34 -0
- package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts.map +1 -1
- package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte +24 -0
- package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte.d.ts +11 -0
- package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte +69 -4
- package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte.d.ts +32 -0
- package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte.d.ts.map +1 -1
- package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte +44 -0
- package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte.d.ts +9 -0
- package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/policy-field.fixture.svelte +35 -0
- package/dist/components/forms/__tests__/policy-field.fixture.svelte.d.ts +8 -0
- package/dist/components/forms/__tests__/policy-field.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/prepare-field-value.d.ts +5 -0
- package/dist/components/forms/prepare-field-value.d.ts.map +1 -0
- package/dist/components/forms/prepare-field-value.js +15 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/provider-webmcp.test.js +244 -0
- package/dist/state/form-context.d.ts +28 -6
- package/dist/state/form-context.d.ts.map +1 -1
- package/dist/state/form-context.js +75 -7
- package/dist/web/__tests__/provider-ui-registry.fixture.svelte +43 -0
- package/dist/web/__tests__/provider-ui-registry.fixture.svelte.d.ts +13 -0
- package/dist/web/__tests__/provider-ui-registry.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/provider-ui-registry.test.js +235 -0
- package/dist/web/__tests__/webmcp-composed.child.fixture.svelte +62 -0
- package/dist/web/__tests__/webmcp-composed.child.fixture.svelte.d.ts +8 -0
- package/dist/web/__tests__/webmcp-composed.child.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/webmcp-composed.fixture.svelte +52 -0
- package/dist/web/__tests__/webmcp-composed.fixture.svelte.d.ts +12 -0
- package/dist/web/__tests__/webmcp-composed.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/webmcp-composed.integration.svelte.test.js +247 -0
- package/dist/web/__tests__/webmcp-ssr.fixture.svelte +13 -0
- package/dist/web/__tests__/webmcp-ssr.fixture.svelte.d.ts +19 -0
- package/dist/web/__tests__/webmcp-ssr.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/webmcp-ui.test.js +586 -0
- package/dist/web/__tests__/webmcp.test.js +21 -1
- package/dist/web/index.d.ts +3 -0
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +2 -0
- package/dist/web/webmcp-provider.d.ts +23 -0
- package/dist/web/webmcp-provider.d.ts.map +1 -0
- package/dist/web/webmcp-provider.js +1 -0
- package/dist/web/webmcp-ui-context.d.ts +12 -0
- package/dist/web/webmcp-ui-context.d.ts.map +1 -0
- package/dist/web/webmcp-ui-context.js +16 -0
- package/dist/web/webmcp-ui.d.ts +21 -0
- package/dist/web/webmcp-ui.d.ts.map +1 -0
- package/dist/web/webmcp-ui.js +474 -0
- package/dist/web/webmcp.d.ts +1 -1
- package/dist/web/webmcp.svelte.d.ts +4 -1
- package/dist/web/webmcp.svelte.d.ts.map +1 -1
- package/dist/web/webmcp.svelte.js +6 -1
- package/package.json +8 -6
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DataSurfaceRegistry } from '@happyvertical/smrt-ui/data';
|
|
2
|
+
import type { ControlInteractionRegistry } from '@happyvertical/smrt-ui/forms';
|
|
3
|
+
export interface WebMcpUiContext {
|
|
4
|
+
readonly enabled: boolean;
|
|
5
|
+
readonly controlRegistry: ControlInteractionRegistry;
|
|
6
|
+
readonly dataSurfaceRegistry: DataSurfaceRegistry;
|
|
7
|
+
}
|
|
8
|
+
export declare function setWebMcpUiContext(context: WebMcpUiContext): void;
|
|
9
|
+
export declare function tryGetWebMcpUiContext(): WebMcpUiContext | null;
|
|
10
|
+
/** Return the mounted-UI registries owned by the nearest SMRT Provider. */
|
|
11
|
+
export declare function useWebMcpUi(): WebMcpUiContext;
|
|
12
|
+
//# sourceMappingURL=webmcp-ui-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webmcp-ui-context.d.ts","sourceRoot":"","sources":["../../src/web/webmcp-ui-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAK/E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,eAAe,EAAE,0BAA0B,CAAC;IACrD,QAAQ,CAAC,mBAAmB,EAAE,mBAAmB,CAAC;CACnD;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAEjE;AAED,wBAAgB,qBAAqB,IAAI,eAAe,GAAG,IAAI,CAE9D;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,IAAI,eAAe,CAQ7C"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getContext, setContext } from 'svelte';
|
|
2
|
+
const WEBMCP_UI_CONTEXT_KEY = Symbol('smrt-webmcp-ui-context');
|
|
3
|
+
export function setWebMcpUiContext(context) {
|
|
4
|
+
setContext(WEBMCP_UI_CONTEXT_KEY, context);
|
|
5
|
+
}
|
|
6
|
+
export function tryGetWebMcpUiContext() {
|
|
7
|
+
return getContext(WEBMCP_UI_CONTEXT_KEY) ?? null;
|
|
8
|
+
}
|
|
9
|
+
/** Return the mounted-UI registries owned by the nearest SMRT Provider. */
|
|
10
|
+
export function useWebMcpUi() {
|
|
11
|
+
const context = tryGetWebMcpUiContext();
|
|
12
|
+
if (!context?.enabled) {
|
|
13
|
+
throw new Error('WebMCP UI context not found. Wrap this component with <Provider>.');
|
|
14
|
+
}
|
|
15
|
+
return context;
|
|
16
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type DataSurfaceRegistry } from '@happyvertical/smrt-ui/data';
|
|
2
|
+
import type { ControlInteractionRegistry } from '@happyvertical/smrt-ui/forms';
|
|
3
|
+
import type { WebMcpToolSpec } from './webmcp.svelte.js';
|
|
4
|
+
export interface RegisterWebMcpUiToolsOptions {
|
|
5
|
+
controlRegistry: ControlInteractionRegistry;
|
|
6
|
+
dataSurfaceRegistry: DataSurfaceRegistry;
|
|
7
|
+
prefix?: string;
|
|
8
|
+
/** Injectable browser document used by tests and non-window hosts. */
|
|
9
|
+
document?: {
|
|
10
|
+
modelContext?: WebMcpModelContextLike;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
interface WebMcpModelContextLike {
|
|
14
|
+
registerTool(tool: WebMcpToolSpec, options?: {
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
}): void | Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
/** Register the fixed browser-native adapter over mounted UI registries. */
|
|
19
|
+
export declare function registerWebMcpUiTools(options: RegisterWebMcpUiToolsOptions): () => void;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=webmcp-ui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webmcp-ui.d.ts","sourceRoot":"","sources":["../../src/web/webmcp-ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,mBAAmB,EAGzB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAKV,0BAA0B,EAE3B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AA8CzD,MAAM,WAAW,4BAA4B;IAC3C,eAAe,EAAE,0BAA0B,CAAC;IAC5C,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,QAAQ,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,sBAAsB,CAAA;KAAE,CAAC;CACtD;AAED,UAAU,sBAAsB;IAC9B,YAAY,CACV,IAAI,EAAE,cAAc,EACpB,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GACjC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AA6fD,4EAA4E;AAC5E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,4BAA4B,GACpC,MAAM,IAAI,CAoDZ"}
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import { DATA_SURFACE_IDENTIFIER_MAX_LENGTH, DATA_SURFACE_MAX_REQUEST_BYTES, } from '@happyvertical/smrt-ui/data';
|
|
2
|
+
const CONTROL_ACTIONS = new Set([
|
|
3
|
+
'focus',
|
|
4
|
+
'reveal',
|
|
5
|
+
'highlight',
|
|
6
|
+
'explain',
|
|
7
|
+
'validate',
|
|
8
|
+
'stage',
|
|
9
|
+
'apply',
|
|
10
|
+
'clear',
|
|
11
|
+
'undo',
|
|
12
|
+
]);
|
|
13
|
+
const DEFAULT_PREFIX = 'smrt_ui_';
|
|
14
|
+
const PREFIX_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
|
|
15
|
+
const PUBLIC_FAILURE_REASONS = new Set([
|
|
16
|
+
'invalid_request',
|
|
17
|
+
'invalid_identifier',
|
|
18
|
+
'limit_exceeded',
|
|
19
|
+
'not_found',
|
|
20
|
+
]);
|
|
21
|
+
const PUBLIC_CONTROL_RESULT_REASONS = new Set([
|
|
22
|
+
'not_found',
|
|
23
|
+
'consent_required',
|
|
24
|
+
'human_confirmation_required',
|
|
25
|
+
'sensitive_control',
|
|
26
|
+
'control_not_writable',
|
|
27
|
+
'control_not_editable',
|
|
28
|
+
'nothing_to_undo',
|
|
29
|
+
'denied',
|
|
30
|
+
]);
|
|
31
|
+
const documentLocks = new WeakMap();
|
|
32
|
+
class PublicToolError extends Error {
|
|
33
|
+
reason;
|
|
34
|
+
details;
|
|
35
|
+
constructor(reason, details) {
|
|
36
|
+
super(reason);
|
|
37
|
+
this.reason = reason;
|
|
38
|
+
this.details = details;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function publicError(reason, details) {
|
|
42
|
+
return new PublicToolError(reason, details);
|
|
43
|
+
}
|
|
44
|
+
function success(result) {
|
|
45
|
+
return JSON.stringify({ ok: true, result });
|
|
46
|
+
}
|
|
47
|
+
function failure(reason, details) {
|
|
48
|
+
return JSON.stringify({
|
|
49
|
+
ok: false,
|
|
50
|
+
reason,
|
|
51
|
+
...(details ? { details } : {}),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function requestBytes(value) {
|
|
55
|
+
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
56
|
+
}
|
|
57
|
+
function assertRequestSize(value) {
|
|
58
|
+
if (requestBytes(value) > DATA_SURFACE_MAX_REQUEST_BYTES) {
|
|
59
|
+
throw publicError('limit_exceeded');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function requiredIdentifier(value, name, maxLength = DATA_SURFACE_IDENTIFIER_MAX_LENGTH) {
|
|
63
|
+
if (typeof value !== 'string' ||
|
|
64
|
+
value.length === 0 ||
|
|
65
|
+
value.length > maxLength ||
|
|
66
|
+
Array.from(value).some((character) => {
|
|
67
|
+
const code = character.charCodeAt(0);
|
|
68
|
+
return code < 32 || code === 127;
|
|
69
|
+
})) {
|
|
70
|
+
throw publicError('invalid_identifier', name);
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
function optionalIdentifier(value, name) {
|
|
75
|
+
return value === undefined ? undefined : requiredIdentifier(value, name);
|
|
76
|
+
}
|
|
77
|
+
function record(value) {
|
|
78
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
79
|
+
throw publicError('invalid_request');
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
function controlIdentity(value) {
|
|
84
|
+
const input = record(value);
|
|
85
|
+
const subjectInput = input.subject;
|
|
86
|
+
const subject = subjectInput === undefined
|
|
87
|
+
? undefined
|
|
88
|
+
: (() => {
|
|
89
|
+
const next = record(subjectInput);
|
|
90
|
+
return {
|
|
91
|
+
type: requiredIdentifier(next.type, 'subject.type'),
|
|
92
|
+
id: requiredIdentifier(next.id, 'subject.id'),
|
|
93
|
+
...(next.label === undefined
|
|
94
|
+
? {}
|
|
95
|
+
: { label: requiredIdentifier(next.label, 'subject.label') }),
|
|
96
|
+
};
|
|
97
|
+
})();
|
|
98
|
+
return {
|
|
99
|
+
formId: requiredIdentifier(input.formId, 'formId'),
|
|
100
|
+
controlId: requiredIdentifier(input.controlId, 'controlId'),
|
|
101
|
+
...(subject ? { subject } : {}),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function dataSurfaceIdentity(value) {
|
|
105
|
+
const input = record(value);
|
|
106
|
+
const kind = input.kind;
|
|
107
|
+
if (!['table', 'list', 'report', 'custom'].includes(String(kind))) {
|
|
108
|
+
throw publicError('invalid_request', 'identity.kind');
|
|
109
|
+
}
|
|
110
|
+
const subjectInput = input.subject;
|
|
111
|
+
const subject = subjectInput === undefined
|
|
112
|
+
? undefined
|
|
113
|
+
: (() => {
|
|
114
|
+
const next = record(subjectInput);
|
|
115
|
+
return {
|
|
116
|
+
type: requiredIdentifier(next.type, 'subject.type'),
|
|
117
|
+
id: requiredIdentifier(next.id, 'subject.id'),
|
|
118
|
+
...(next.label === undefined
|
|
119
|
+
? {}
|
|
120
|
+
: { label: requiredIdentifier(next.label, 'subject.label') }),
|
|
121
|
+
};
|
|
122
|
+
})();
|
|
123
|
+
return {
|
|
124
|
+
surfaceId: requiredIdentifier(input.surfaceId, 'surfaceId'),
|
|
125
|
+
kind: kind,
|
|
126
|
+
...(subject ? { subject } : {}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function sanitizeControl(snapshot) {
|
|
130
|
+
const sensitivityRedacted = snapshot.metadata.sensitivity === 'sensitive' ||
|
|
131
|
+
snapshot.metadata.sensitivity === 'secret';
|
|
132
|
+
const redactValue = sensitivityRedacted || snapshot.state.valueRedacted;
|
|
133
|
+
const redactStagedValue = sensitivityRedacted ||
|
|
134
|
+
snapshot.state.stagedValueRedacted ||
|
|
135
|
+
snapshot.state.staged?.valueRedacted === true;
|
|
136
|
+
const staged = snapshot.state.staged
|
|
137
|
+
? {
|
|
138
|
+
...snapshot.state.staged,
|
|
139
|
+
...(redactStagedValue ? { value: undefined, valueRedacted: true } : {}),
|
|
140
|
+
validationMessage: undefined,
|
|
141
|
+
}
|
|
142
|
+
: undefined;
|
|
143
|
+
return {
|
|
144
|
+
...snapshot,
|
|
145
|
+
identity: { ...snapshot.identity },
|
|
146
|
+
metadata: {
|
|
147
|
+
...snapshot.metadata,
|
|
148
|
+
constraints: snapshot.metadata.constraints
|
|
149
|
+
? { ...snapshot.metadata.constraints }
|
|
150
|
+
: undefined,
|
|
151
|
+
options: snapshot.metadata.options?.map((option) => ({ ...option })),
|
|
152
|
+
},
|
|
153
|
+
state: {
|
|
154
|
+
...snapshot.state,
|
|
155
|
+
...(redactValue ? { value: undefined, valueRedacted: true } : {}),
|
|
156
|
+
...(redactStagedValue
|
|
157
|
+
? { stagedValue: undefined, stagedValueRedacted: true }
|
|
158
|
+
: {}),
|
|
159
|
+
...(staged ? { staged } : {}),
|
|
160
|
+
validationMessage: undefined,
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function sanitizeControlResult(result) {
|
|
165
|
+
const reason = result.reason
|
|
166
|
+
? PUBLIC_CONTROL_RESULT_REASONS.has(result.reason)
|
|
167
|
+
? result.reason
|
|
168
|
+
: 'execution_failed'
|
|
169
|
+
: undefined;
|
|
170
|
+
return {
|
|
171
|
+
ok: result.ok,
|
|
172
|
+
action: result.action,
|
|
173
|
+
identity: { ...result.identity },
|
|
174
|
+
...(reason ? { reason } : {}),
|
|
175
|
+
...(result.snapshot ? { snapshot: sanitizeControl(result.snapshot) } : {}),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function sanitizeSurfaceValue(value, hiddenColumnIds, redactRowIds, parentKey) {
|
|
179
|
+
if (Array.isArray(value)) {
|
|
180
|
+
return value
|
|
181
|
+
.filter((entry) => parentKey !== 'columnOrder' ||
|
|
182
|
+
typeof entry !== 'string' ||
|
|
183
|
+
!hiddenColumnIds.has(entry))
|
|
184
|
+
.map((entry) => sanitizeSurfaceValue(entry, hiddenColumnIds, redactRowIds, parentKey))
|
|
185
|
+
.filter((entry) => entry !== undefined);
|
|
186
|
+
}
|
|
187
|
+
if (!value || typeof value !== 'object')
|
|
188
|
+
return value;
|
|
189
|
+
const object = value;
|
|
190
|
+
if (typeof object.columnId === 'string' &&
|
|
191
|
+
hiddenColumnIds.has(object.columnId)) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
return Object.fromEntries(Object.entries(object).flatMap(([key, entry]) => {
|
|
195
|
+
if (hiddenColumnIds.has(key))
|
|
196
|
+
return [];
|
|
197
|
+
if (redactRowIds &&
|
|
198
|
+
(key === 'selection' ||
|
|
199
|
+
key === 'selectedRowIds' ||
|
|
200
|
+
key === 'expandedRowIds')) {
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
const sanitized = sanitizeSurfaceValue(entry, hiddenColumnIds, redactRowIds, key);
|
|
204
|
+
return sanitized === undefined ? [] : [[key, sanitized]];
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
function visibleDescriptor(descriptor) {
|
|
208
|
+
const columns = descriptor.columns.filter((column) => column.visibility !== 'hidden');
|
|
209
|
+
const visibleColumnIds = new Set(columns.map((column) => column.id));
|
|
210
|
+
const { rowKey, ...visible } = descriptor;
|
|
211
|
+
return {
|
|
212
|
+
...visible,
|
|
213
|
+
identity: { ...descriptor.identity },
|
|
214
|
+
...(visibleColumnIds.has(rowKey) ? { rowKey } : {}),
|
|
215
|
+
columns,
|
|
216
|
+
query: {
|
|
217
|
+
...descriptor.query,
|
|
218
|
+
projectableColumnIds: descriptor.query.projectableColumnIds.filter((id) => visibleColumnIds.has(id)),
|
|
219
|
+
searchableColumnIds: descriptor.query.searchableColumnIds?.filter((id) => visibleColumnIds.has(id)),
|
|
220
|
+
filterableColumnIds: descriptor.query.filterableColumnIds?.filter((id) => visibleColumnIds.has(id)),
|
|
221
|
+
sortableColumnIds: descriptor.query.sortableColumnIds?.filter((id) => visibleColumnIds.has(id)),
|
|
222
|
+
},
|
|
223
|
+
actions: descriptor.actions.filter((action) => (action.columnIds ?? []).every((id) => visibleColumnIds.has(id))),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function visibleSnapshot(snapshot) {
|
|
227
|
+
const hiddenColumnIds = new Set(snapshot.descriptor.columns
|
|
228
|
+
.filter((column) => column.visibility === 'hidden')
|
|
229
|
+
.map((column) => column.id));
|
|
230
|
+
const rowKeyHidden = hiddenColumnIds.has(snapshot.descriptor.rowKey);
|
|
231
|
+
const state = sanitizeSurfaceValue(snapshot.state, hiddenColumnIds, rowKeyHidden);
|
|
232
|
+
return {
|
|
233
|
+
...snapshot,
|
|
234
|
+
descriptor: visibleDescriptor(snapshot.descriptor),
|
|
235
|
+
state,
|
|
236
|
+
selection: rowKeyHidden ? null : snapshot.selection,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function executeSafely(execute) {
|
|
240
|
+
return Promise.resolve()
|
|
241
|
+
.then(execute)
|
|
242
|
+
.then(success)
|
|
243
|
+
.catch((error) => {
|
|
244
|
+
return error instanceof PublicToolError &&
|
|
245
|
+
PUBLIC_FAILURE_REASONS.has(error.reason)
|
|
246
|
+
? failure(error.reason, error.details)
|
|
247
|
+
: failure('execution_failed');
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
function readTool(name, description, inputSchema, execute) {
|
|
251
|
+
return {
|
|
252
|
+
name,
|
|
253
|
+
description,
|
|
254
|
+
inputSchema,
|
|
255
|
+
annotations: { readOnlyHint: true, untrustedContentHint: true },
|
|
256
|
+
execute,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function tools(prefix, controlRegistry, dataSurfaceRegistry) {
|
|
260
|
+
const identitySchema = {
|
|
261
|
+
type: 'object',
|
|
262
|
+
required: ['formId', 'controlId'],
|
|
263
|
+
additionalProperties: false,
|
|
264
|
+
properties: {
|
|
265
|
+
formId: { type: 'string', minLength: 1, maxLength: 256 },
|
|
266
|
+
controlId: { type: 'string', minLength: 1, maxLength: 256 },
|
|
267
|
+
subject: { type: 'object' },
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
const surfaceIdentitySchema = {
|
|
271
|
+
type: 'object',
|
|
272
|
+
required: ['surfaceId', 'kind'],
|
|
273
|
+
additionalProperties: false,
|
|
274
|
+
properties: {
|
|
275
|
+
surfaceId: { type: 'string', minLength: 1, maxLength: 256 },
|
|
276
|
+
kind: { type: 'string', enum: ['table', 'list', 'report', 'custom'] },
|
|
277
|
+
subject: { type: 'object' },
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
return [
|
|
281
|
+
readTool(`${prefix}list_form_controls`, 'List the controls currently mounted in SMRT forms.', {
|
|
282
|
+
type: 'object',
|
|
283
|
+
additionalProperties: false,
|
|
284
|
+
properties: {
|
|
285
|
+
formId: { type: 'string', minLength: 1, maxLength: 256 },
|
|
286
|
+
},
|
|
287
|
+
}, (args) => executeSafely(() => {
|
|
288
|
+
assertRequestSize(args);
|
|
289
|
+
const input = record(args);
|
|
290
|
+
const formId = optionalIdentifier(input.formId, 'formId');
|
|
291
|
+
return controlRegistry.list(formId).map(sanitizeControl);
|
|
292
|
+
})),
|
|
293
|
+
readTool(`${prefix}inspect_form_control`, 'Inspect one currently mounted SMRT form control.', {
|
|
294
|
+
type: 'object',
|
|
295
|
+
required: ['identity'],
|
|
296
|
+
additionalProperties: false,
|
|
297
|
+
properties: { identity: identitySchema },
|
|
298
|
+
}, (args) => executeSafely(() => {
|
|
299
|
+
assertRequestSize(args);
|
|
300
|
+
const input = record(args);
|
|
301
|
+
const snapshot = controlRegistry.get(controlIdentity(input.identity));
|
|
302
|
+
if (!snapshot)
|
|
303
|
+
throw publicError('not_found');
|
|
304
|
+
return sanitizeControl(snapshot);
|
|
305
|
+
})),
|
|
306
|
+
{
|
|
307
|
+
name: `${prefix}execute_form_control`,
|
|
308
|
+
description: 'Execute an allowed command on a mounted SMRT form control. Agent mutations are consent-gated.',
|
|
309
|
+
inputSchema: {
|
|
310
|
+
type: 'object',
|
|
311
|
+
required: ['action', 'identity'],
|
|
312
|
+
additionalProperties: false,
|
|
313
|
+
properties: {
|
|
314
|
+
action: { type: 'string', enum: [...CONTROL_ACTIONS] },
|
|
315
|
+
identity: identitySchema,
|
|
316
|
+
value: {},
|
|
317
|
+
durationMs: { type: 'number', minimum: 0, maximum: 60_000 },
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
annotations: { readOnlyHint: false, untrustedContentHint: true },
|
|
321
|
+
execute: (args) => executeSafely(async () => {
|
|
322
|
+
assertRequestSize(args);
|
|
323
|
+
const input = record(args);
|
|
324
|
+
if ('confirmed' in input)
|
|
325
|
+
throw publicError('invalid_request', 'confirmed');
|
|
326
|
+
if (!CONTROL_ACTIONS.has(input.action)) {
|
|
327
|
+
throw publicError('invalid_request', 'action');
|
|
328
|
+
}
|
|
329
|
+
const action = input.action;
|
|
330
|
+
const identity = controlIdentity(input.identity);
|
|
331
|
+
let command;
|
|
332
|
+
if (action === 'stage') {
|
|
333
|
+
if (!('value' in input))
|
|
334
|
+
throw publicError('invalid_request', 'value');
|
|
335
|
+
command = { action, identity, value: input.value };
|
|
336
|
+
}
|
|
337
|
+
else if (action === 'apply') {
|
|
338
|
+
command =
|
|
339
|
+
'value' in input
|
|
340
|
+
? { action, identity, value: input.value }
|
|
341
|
+
: { action, identity };
|
|
342
|
+
}
|
|
343
|
+
else if (action === 'highlight') {
|
|
344
|
+
if (input.durationMs !== undefined &&
|
|
345
|
+
(typeof input.durationMs !== 'number' ||
|
|
346
|
+
input.durationMs < 0 ||
|
|
347
|
+
input.durationMs > 60_000)) {
|
|
348
|
+
throw publicError('invalid_request', 'durationMs');
|
|
349
|
+
}
|
|
350
|
+
command = { action, identity, durationMs: input.durationMs };
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
command = { action, identity };
|
|
354
|
+
}
|
|
355
|
+
const result = await controlRegistry.execute(command, {
|
|
356
|
+
source: 'agent',
|
|
357
|
+
});
|
|
358
|
+
return sanitizeControlResult(result);
|
|
359
|
+
}),
|
|
360
|
+
},
|
|
361
|
+
readTool(`${prefix}list_data_surfaces`, 'List the data surfaces currently mounted in this SMRT Provider.', { type: 'object', additionalProperties: false, properties: {} }, (args) => executeSafely(() => {
|
|
362
|
+
assertRequestSize(args);
|
|
363
|
+
record(args);
|
|
364
|
+
return dataSurfaceRegistry.list().map(visibleDescriptor);
|
|
365
|
+
})),
|
|
366
|
+
readTool(`${prefix}inspect_data_surface`, 'Inspect one currently mounted SMRT data surface.', {
|
|
367
|
+
type: 'object',
|
|
368
|
+
required: ['identity'],
|
|
369
|
+
additionalProperties: false,
|
|
370
|
+
properties: { identity: surfaceIdentitySchema },
|
|
371
|
+
}, (args) => executeSafely(() => {
|
|
372
|
+
assertRequestSize(args);
|
|
373
|
+
const input = record(args);
|
|
374
|
+
const snapshot = dataSurfaceRegistry.inspect(dataSurfaceIdentity(input.identity));
|
|
375
|
+
if (!snapshot)
|
|
376
|
+
throw publicError('not_found');
|
|
377
|
+
return visibleSnapshot(snapshot);
|
|
378
|
+
})),
|
|
379
|
+
{
|
|
380
|
+
name: `${prefix}execute_data_surface_control`,
|
|
381
|
+
description: 'Execute a bounded visible-state command on a mounted SMRT data surface.',
|
|
382
|
+
inputSchema: {
|
|
383
|
+
type: 'object',
|
|
384
|
+
required: [
|
|
385
|
+
'version',
|
|
386
|
+
'commandId',
|
|
387
|
+
'identity',
|
|
388
|
+
'expectedRevision',
|
|
389
|
+
'controlId',
|
|
390
|
+
],
|
|
391
|
+
additionalProperties: false,
|
|
392
|
+
properties: {
|
|
393
|
+
version: { type: 'number', const: 1 },
|
|
394
|
+
commandId: { type: 'string', minLength: 1, maxLength: 256 },
|
|
395
|
+
identity: surfaceIdentitySchema,
|
|
396
|
+
expectedRevision: { type: 'number', minimum: 0 },
|
|
397
|
+
controlId: { type: 'string', minLength: 1, maxLength: 256 },
|
|
398
|
+
payload: {},
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
annotations: { readOnlyHint: false, untrustedContentHint: true },
|
|
402
|
+
execute: (args) => executeSafely(async () => {
|
|
403
|
+
assertRequestSize(args);
|
|
404
|
+
const input = record(args);
|
|
405
|
+
if (input.version !== 1)
|
|
406
|
+
throw publicError('invalid_request', 'version');
|
|
407
|
+
if (typeof input.expectedRevision !== 'number' ||
|
|
408
|
+
!Number.isSafeInteger(input.expectedRevision) ||
|
|
409
|
+
input.expectedRevision < 0) {
|
|
410
|
+
throw publicError('invalid_request', 'expectedRevision');
|
|
411
|
+
}
|
|
412
|
+
const command = {
|
|
413
|
+
version: 1,
|
|
414
|
+
commandId: requiredIdentifier(input.commandId, 'commandId'),
|
|
415
|
+
identity: dataSurfaceIdentity(input.identity),
|
|
416
|
+
expectedRevision: input.expectedRevision,
|
|
417
|
+
controlId: requiredIdentifier(input.controlId, 'controlId'),
|
|
418
|
+
...('payload' in input ? { payload: input.payload } : {}),
|
|
419
|
+
};
|
|
420
|
+
const result = await dataSurfaceRegistry.execute(command);
|
|
421
|
+
return result.snapshot
|
|
422
|
+
? { ...result, snapshot: visibleSnapshot(result.snapshot) }
|
|
423
|
+
: result;
|
|
424
|
+
}),
|
|
425
|
+
},
|
|
426
|
+
];
|
|
427
|
+
}
|
|
428
|
+
/** Register the fixed browser-native adapter over mounted UI registries. */
|
|
429
|
+
export function registerWebMcpUiTools(options) {
|
|
430
|
+
const prefix = options.prefix ?? DEFAULT_PREFIX;
|
|
431
|
+
if (!PREFIX_PATTERN.test(prefix)) {
|
|
432
|
+
throw new TypeError('Invalid WebMCP UI tool prefix');
|
|
433
|
+
}
|
|
434
|
+
const documentLike = options.document ??
|
|
435
|
+
globalThis
|
|
436
|
+
.document;
|
|
437
|
+
const modelContext = documentLike?.modelContext;
|
|
438
|
+
if (!modelContext || typeof modelContext.registerTool !== 'function') {
|
|
439
|
+
return () => { };
|
|
440
|
+
}
|
|
441
|
+
let locks = documentLocks.get(documentLike);
|
|
442
|
+
if (!locks) {
|
|
443
|
+
locks = new Set();
|
|
444
|
+
documentLocks.set(documentLike, locks);
|
|
445
|
+
}
|
|
446
|
+
if (locks.has(prefix)) {
|
|
447
|
+
throw new Error(`WebMCP UI prefix is already registered: ${prefix}`);
|
|
448
|
+
}
|
|
449
|
+
locks.add(prefix);
|
|
450
|
+
const controller = new AbortController();
|
|
451
|
+
let disposed = false;
|
|
452
|
+
const dispose = () => {
|
|
453
|
+
if (disposed)
|
|
454
|
+
return;
|
|
455
|
+
disposed = true;
|
|
456
|
+
controller.abort();
|
|
457
|
+
locks.delete(prefix);
|
|
458
|
+
};
|
|
459
|
+
try {
|
|
460
|
+
for (const tool of tools(prefix, options.controlRegistry, options.dataSurfaceRegistry)) {
|
|
461
|
+
const registration = modelContext.registerTool(tool, {
|
|
462
|
+
signal: controller.signal,
|
|
463
|
+
});
|
|
464
|
+
if (registration) {
|
|
465
|
+
void Promise.resolve(registration).catch(dispose);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
dispose();
|
|
471
|
+
throw error;
|
|
472
|
+
}
|
|
473
|
+
return dispose;
|
|
474
|
+
}
|
package/dist/web/webmcp.d.ts
CHANGED
|
@@ -5,6 +5,9 @@ export interface WebMcpToolSpec {
|
|
|
5
5
|
inputSchema: Record<string, unknown>;
|
|
6
6
|
annotations?: {
|
|
7
7
|
readOnlyHint?: boolean;
|
|
8
|
+
destructiveHint?: boolean;
|
|
9
|
+
idempotentHint?: boolean;
|
|
10
|
+
openWorldHint?: boolean;
|
|
8
11
|
untrustedContentHint?: boolean;
|
|
9
12
|
};
|
|
10
13
|
execute: (args: Record<string, unknown>) => string | Promise<string>;
|
|
@@ -16,7 +19,7 @@ declare global {
|
|
|
16
19
|
interface WebMcpModelContext {
|
|
17
20
|
registerTool(tool: WebMcpToolSpec, options?: {
|
|
18
21
|
signal?: AbortSignal;
|
|
19
|
-
}): void
|
|
22
|
+
}): Promise<void>;
|
|
20
23
|
}
|
|
21
24
|
}
|
|
22
25
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webmcp.svelte.d.ts","sourceRoot":"","sources":["../../src/web/webmcp.svelte.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE;QACZ,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,oBAAoB,CAAC,EAAE,OAAO,CAAC;KAChC,CAAC;IACF,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACtE;AAID,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,QAAQ;QAChB,YAAY,CAAC,EAAE,kBAAkB,CAAC;KACnC;IAED,UAAU,kBAAkB;QAC1B,YAAY,CACV,IAAI,EAAE,cAAc,EACpB,OAAO,CAAC,EAAE;YAAE,MAAM,CAAC,EAAE,WAAW,CAAA;SAAE,GACjC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"webmcp.svelte.d.ts","sourceRoot":"","sources":["../../src/web/webmcp.svelte.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE;QACZ,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,oBAAoB,CAAC,EAAE,OAAO,CAAC;KAChC,CAAC;IACF,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACtE;AAID,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,QAAQ;QAChB,YAAY,CAAC,EAAE,kBAAkB,CAAC;KACnC;IAED,UAAU,kBAAkB;QAC1B,YAAY,CACV,IAAI,EAAE,cAAc,EACpB,OAAO,CAAC,EAAE;YAAE,MAAM,CAAC,EAAE,WAAW,CAAA;SAAE,GACjC,OAAO,CAAC,IAAI,CAAC,CAAC;KAClB;CACF;AAYD;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,cAAc,GAAG,IAAI,GAAG,SAAS,GAC/C,IAAI,CAiBN"}
|
|
@@ -21,7 +21,12 @@ export function useWebMcpTool(factory) {
|
|
|
21
21
|
if (!spec)
|
|
22
22
|
return;
|
|
23
23
|
const controller = new AbortController();
|
|
24
|
-
context.registerTool(spec, {
|
|
24
|
+
const registration = context.registerTool(spec, {
|
|
25
|
+
signal: controller.signal,
|
|
26
|
+
});
|
|
27
|
+
if (registration) {
|
|
28
|
+
void Promise.resolve(registration).catch(() => controller.abort());
|
|
29
|
+
}
|
|
25
30
|
return () => controller.abort();
|
|
26
31
|
});
|
|
27
32
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-svelte",
|
|
3
|
-
"version": "0.43.
|
|
3
|
+
"version": "0.43.5",
|
|
4
4
|
"description": "Svelte 5 components for SMRT user management - auth, users, tenants, roles, permissions, groups",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"smrtRawPrimitives": "strict",
|
|
@@ -123,10 +123,10 @@
|
|
|
123
123
|
"@tanstack/db": "^0.6.14",
|
|
124
124
|
"@tanstack/svelte-db": "^0.1.91",
|
|
125
125
|
"esm-env": "^1.2.2",
|
|
126
|
-
"@happyvertical/smrt-
|
|
127
|
-
"@happyvertical/smrt-
|
|
128
|
-
"@happyvertical/smrt-
|
|
129
|
-
"@happyvertical/smrt-
|
|
126
|
+
"@happyvertical/smrt-languages": "0.43.5",
|
|
127
|
+
"@happyvertical/smrt-ui": "0.43.5",
|
|
128
|
+
"@happyvertical/smrt-types": "0.43.5",
|
|
129
|
+
"@happyvertical/smrt-web": "0.43.5"
|
|
130
130
|
},
|
|
131
131
|
"peerDependencies": {
|
|
132
132
|
"@huggingface/transformers": ">=3.8.1",
|
|
@@ -165,7 +165,9 @@
|
|
|
165
165
|
"svelte-check": "^4.7.1",
|
|
166
166
|
"typescript": "5.9.3",
|
|
167
167
|
"vite": "8.1.4",
|
|
168
|
-
"vitest": "4.1.10"
|
|
168
|
+
"vitest": "4.1.10",
|
|
169
|
+
"@happyvertical/smrt-core": "0.43.5",
|
|
170
|
+
"@happyvertical/smrt-scanner": "0.43.5"
|
|
169
171
|
},
|
|
170
172
|
"scripts": {
|
|
171
173
|
"build": "svelte-package -i src --tsconfig tsconfig.svelte.json",
|