@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.
Files changed (118) hide show
  1. package/AGENTS.md +45 -0
  2. package/README.md +73 -0
  3. package/dist/Provider.svelte +83 -26
  4. package/dist/Provider.svelte.d.ts +4 -12
  5. package/dist/Provider.svelte.d.ts.map +1 -1
  6. package/dist/__tests__/provider-webmcp-harness.svelte +14 -0
  7. package/dist/__tests__/provider-webmcp-harness.svelte.d.ts +8 -0
  8. package/dist/__tests__/provider-webmcp-harness.svelte.d.ts.map +1 -0
  9. package/dist/components/forms/AddressInput.svelte +155 -42
  10. package/dist/components/forms/AddressInput.svelte.d.ts.map +1 -1
  11. package/dist/components/forms/CheckboxInput.svelte +40 -13
  12. package/dist/components/forms/CheckboxInput.svelte.d.ts.map +1 -1
  13. package/dist/components/forms/DateRangeInput.svelte +132 -16
  14. package/dist/components/forms/DateRangeInput.svelte.d.ts.map +1 -1
  15. package/dist/components/forms/DateTimeInput.svelte +48 -14
  16. package/dist/components/forms/DateTimeInput.svelte.d.ts.map +1 -1
  17. package/dist/components/forms/Form.svelte +667 -61
  18. package/dist/components/forms/Form.svelte.d.ts +6 -2
  19. package/dist/components/forms/Form.svelte.d.ts.map +1 -1
  20. package/dist/components/forms/MeasurementInput.svelte +137 -31
  21. package/dist/components/forms/MeasurementInput.svelte.d.ts.map +1 -1
  22. package/dist/components/forms/MoneyInput.svelte +42 -23
  23. package/dist/components/forms/MoneyInput.svelte.d.ts.map +1 -1
  24. package/dist/components/forms/NumberInput.svelte +59 -27
  25. package/dist/components/forms/NumberInput.svelte.d.ts.map +1 -1
  26. package/dist/components/forms/PhoneInput.svelte +51 -22
  27. package/dist/components/forms/PhoneInput.svelte.d.ts.map +1 -1
  28. package/dist/components/forms/SelectInput.svelte +38 -22
  29. package/dist/components/forms/SelectInput.svelte.d.ts.map +1 -1
  30. package/dist/components/forms/TextInput.svelte +32 -18
  31. package/dist/components/forms/TextInput.svelte.d.ts.map +1 -1
  32. package/dist/components/forms/TextareaInput.svelte +25 -18
  33. package/dist/components/forms/TextareaInput.svelte.d.ts.map +1 -1
  34. package/dist/components/forms/__tests__/AddressInput.behavior.test.js +94 -0
  35. package/dist/components/forms/__tests__/Form.behavior.test.js +1 -1
  36. package/dist/components/forms/__tests__/Form.extraction.test.js +68 -0
  37. package/dist/components/forms/__tests__/Form.test.js +28 -0
  38. package/dist/components/forms/__tests__/Form.webmcp.test.js +2120 -111
  39. package/dist/components/forms/__tests__/FormMicButton.test.js +0 -1
  40. package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte +30 -0
  41. package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte.d.ts +7 -0
  42. package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte.d.ts.map +1 -0
  43. package/dist/components/forms/__tests__/async-validation-form.fixture.svelte +19 -0
  44. package/dist/components/forms/__tests__/async-validation-form.fixture.svelte.d.ts +9 -0
  45. package/dist/components/forms/__tests__/async-validation-form.fixture.svelte.d.ts.map +1 -0
  46. package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte +21 -0
  47. package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte.d.ts +8 -0
  48. package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte.d.ts.map +1 -0
  49. package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte +27 -0
  50. package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte.d.ts +9 -0
  51. package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte.d.ts.map +1 -0
  52. package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte +48 -0
  53. package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte.d.ts +10 -0
  54. package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte.d.ts.map +1 -0
  55. package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte +26 -0
  56. package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte.d.ts +13 -0
  57. package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte.d.ts.map +1 -0
  58. package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte +52 -0
  59. package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte.d.ts +16 -0
  60. package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte.d.ts.map +1 -0
  61. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte +108 -5
  62. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts +34 -0
  63. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts.map +1 -1
  64. package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte +24 -0
  65. package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte.d.ts +11 -0
  66. package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte.d.ts.map +1 -0
  67. package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte +69 -4
  68. package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte.d.ts +32 -0
  69. package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte.d.ts.map +1 -1
  70. package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte +44 -0
  71. package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte.d.ts +9 -0
  72. package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte.d.ts.map +1 -0
  73. package/dist/components/forms/__tests__/policy-field.fixture.svelte +35 -0
  74. package/dist/components/forms/__tests__/policy-field.fixture.svelte.d.ts +8 -0
  75. package/dist/components/forms/__tests__/policy-field.fixture.svelte.d.ts.map +1 -0
  76. package/dist/components/forms/prepare-field-value.d.ts +5 -0
  77. package/dist/components/forms/prepare-field-value.d.ts.map +1 -0
  78. package/dist/components/forms/prepare-field-value.js +15 -0
  79. package/dist/index.d.ts +3 -0
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +2 -0
  82. package/dist/provider-webmcp.test.js +244 -0
  83. package/dist/state/form-context.d.ts +28 -6
  84. package/dist/state/form-context.d.ts.map +1 -1
  85. package/dist/state/form-context.js +75 -7
  86. package/dist/web/__tests__/provider-ui-registry.fixture.svelte +43 -0
  87. package/dist/web/__tests__/provider-ui-registry.fixture.svelte.d.ts +13 -0
  88. package/dist/web/__tests__/provider-ui-registry.fixture.svelte.d.ts.map +1 -0
  89. package/dist/web/__tests__/provider-ui-registry.test.js +235 -0
  90. package/dist/web/__tests__/webmcp-composed.child.fixture.svelte +62 -0
  91. package/dist/web/__tests__/webmcp-composed.child.fixture.svelte.d.ts +8 -0
  92. package/dist/web/__tests__/webmcp-composed.child.fixture.svelte.d.ts.map +1 -0
  93. package/dist/web/__tests__/webmcp-composed.fixture.svelte +52 -0
  94. package/dist/web/__tests__/webmcp-composed.fixture.svelte.d.ts +12 -0
  95. package/dist/web/__tests__/webmcp-composed.fixture.svelte.d.ts.map +1 -0
  96. package/dist/web/__tests__/webmcp-composed.integration.svelte.test.js +247 -0
  97. package/dist/web/__tests__/webmcp-ssr.fixture.svelte +13 -0
  98. package/dist/web/__tests__/webmcp-ssr.fixture.svelte.d.ts +19 -0
  99. package/dist/web/__tests__/webmcp-ssr.fixture.svelte.d.ts.map +1 -0
  100. package/dist/web/__tests__/webmcp-ui.test.js +586 -0
  101. package/dist/web/__tests__/webmcp.test.js +21 -1
  102. package/dist/web/index.d.ts +3 -0
  103. package/dist/web/index.d.ts.map +1 -1
  104. package/dist/web/index.js +2 -0
  105. package/dist/web/webmcp-provider.d.ts +23 -0
  106. package/dist/web/webmcp-provider.d.ts.map +1 -0
  107. package/dist/web/webmcp-provider.js +1 -0
  108. package/dist/web/webmcp-ui-context.d.ts +12 -0
  109. package/dist/web/webmcp-ui-context.d.ts.map +1 -0
  110. package/dist/web/webmcp-ui-context.js +16 -0
  111. package/dist/web/webmcp-ui.d.ts +21 -0
  112. package/dist/web/webmcp-ui.d.ts.map +1 -0
  113. package/dist/web/webmcp-ui.js +474 -0
  114. package/dist/web/webmcp.d.ts +1 -1
  115. package/dist/web/webmcp.svelte.d.ts +4 -1
  116. package/dist/web/webmcp.svelte.d.ts.map +1 -1
  117. package/dist/web/webmcp.svelte.js +6 -1
  118. 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
+ }
@@ -14,7 +14,7 @@ declare global {
14
14
  registerTool(
15
15
  tool: WebMcpToolSpec,
16
16
  options?: { signal?: AbortSignal },
17
- ): void;
17
+ ): Promise<void>;
18
18
  }
19
19
  }
20
20
 
@@ -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;KACT;CACF;AAYD;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,cAAc,GAAG,IAAI,GAAG,SAAS,GAC/C,IAAI,CAYN"}
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, { signal: controller.signal });
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",
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-types": "0.43.3",
127
- "@happyvertical/smrt-web": "0.43.3",
128
- "@happyvertical/smrt-ui": "0.43.3",
129
- "@happyvertical/smrt-languages": "0.43.3"
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",