@happyvertical/smrt-svelte 0.43.3 → 0.43.4
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 +22 -0
- package/README.md +64 -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/Form.svelte +112 -35
- package/dist/components/forms/Form.svelte.d.ts.map +1 -1
- package/dist/components/forms/__tests__/Form.webmcp.test.js +5 -5
- 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/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-ui.test.js +410 -0
- package/dist/web/__tests__/webmcp.test.js +1 -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 +455 -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/package.json +5 -5
package/AGENTS.md
CHANGED
|
@@ -16,6 +16,8 @@ subpath you are editing. This file keeps what holds in every module.
|
|
|
16
16
|
| `src/components/settings/` (`./settings`) | `SettingsCatalog`, `paginateSettingsCatalog`, and the summary-vs-detail scalability contract | [agents/settings.md](agents/settings.md) |
|
|
17
17
|
| `src/components/workspace/` (`./workspace` + `./web`) | the AdminShell family and its principles, the legacy ToolsDock surface, the `./web` activity-feed and `updateAvailable` adapters, and server-side dock gates | [agents/workspace.md](agents/workspace.md) |
|
|
18
18
|
| `src/web/remote-query.svelte.ts` | Svelte 5 binding for query-shaped remote pages: rows, page, totals, loading/refreshing/stale/error, retry, last-updated, and query-scoped live subscriptions (#2445) | — |
|
|
19
|
+
| `src/web/webmcp-provider.ts` | Provider config for generated data/model WebMCP tools: definitions, effect policy, namespace, budget, legacy/canonical filters, and fetcher seams (#2520) | — |
|
|
20
|
+
| `src/web/webmcp-ui.ts` | Fixed, low-cardinality WebMCP adapter over the Provider's mounted form-control and data-surface registries (#2521) | — |
|
|
19
21
|
|
|
20
22
|
## The UI split — primitive-adoption contract (#1589)
|
|
21
23
|
|
|
@@ -64,6 +66,26 @@ Wraps app in `+layout.svelte`. Provides auth state, permissions, WebSocket, and
|
|
|
64
66
|
</Provider>
|
|
65
67
|
```
|
|
66
68
|
|
|
69
|
+
An object `webmcp` config with `definitions` registers generated data/model
|
|
70
|
+
tools through the same registrar as direct smrt-web callers. With no `effects`
|
|
71
|
+
policy, only `read`-effect tools register; `write` and `destructive` require
|
|
72
|
+
explicit opt-in. Use `filter` for legacy collection definitions and `filterTool`
|
|
73
|
+
for canonical definitions; a filter supplied for definitions of the other kind
|
|
74
|
+
fails closed. Namespace, budget, resolver, annotation, and atomic-validation
|
|
75
|
+
behavior must stay identical between Provider and direct registration. The
|
|
76
|
+
authenticated REST route remains the execution authorization boundary.
|
|
77
|
+
|
|
78
|
+
With the fixed mounted-UI adapter enabled, Provider also owns shared
|
|
79
|
+
control/data-surface registries and registers six fixed `smrt_ui_*` tools. This
|
|
80
|
+
adapter is a separate, consent-gated surface and is not governed by the
|
|
81
|
+
generated-model effect policy. Descendant rich Forms use the shared
|
|
82
|
+
control registry unless their explicit `interactionRegistry` prop overrides it.
|
|
83
|
+
Supply `webmcp.ui.dataSurfaceRegistry` when mounted surface components already
|
|
84
|
+
share an application registry. The adapter resolves registry state at execution
|
|
85
|
+
time, never reads the DOM, never accepts agent confirmation, and removes the
|
|
86
|
+
entire tool set with one abort signal. A document may have only one active
|
|
87
|
+
adapter for a prefix; configure distinct prefixes for intentional coexistence.
|
|
88
|
+
|
|
67
89
|
## Hooks
|
|
68
90
|
|
|
69
91
|
| Hook | Returns |
|
package/README.md
CHANGED
|
@@ -92,6 +92,70 @@ hydrating its whole collection. It exposes `rows`, `page`, `total`,
|
|
|
92
92
|
</Provider>
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
+
The Provider can own generated WebMCP tools for the same lifecycle. Its policy
|
|
96
|
+
is identical to `registerWebMcpTools`; omitted `effects` exposes reads only:
|
|
97
|
+
|
|
98
|
+
```svelte
|
|
99
|
+
<Provider webmcp={{
|
|
100
|
+
definitions,
|
|
101
|
+
effects: ['read', 'write'],
|
|
102
|
+
namespace: 'workspace',
|
|
103
|
+
maxTools: 24
|
|
104
|
+
}}>
|
|
105
|
+
{@render children()}
|
|
106
|
+
</Provider>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This controls capability exposure, not authorization. Tool execution still
|
|
110
|
+
crosses the authenticated REST boundary and must retain its auth and tenancy
|
|
111
|
+
guards. The `effects`, `filter`, and `filterTool` policy applies to generated
|
|
112
|
+
data/model tools only; the fixed mounted-UI adapter has the separate controls
|
|
113
|
+
described below.
|
|
114
|
+
|
|
115
|
+
WebMCP test doubles and polyfills should implement the browser's
|
|
116
|
+
promise-returning `document.modelContext.registerTool()` contract; declare the
|
|
117
|
+
function `async` when migrating older void-returning fixtures.
|
|
118
|
+
|
|
119
|
+
### Mounted UI through WebMCP
|
|
120
|
+
|
|
121
|
+
`<Provider webmcp>` registers six fixed `smrt_ui_*` tools for the mounted UI:
|
|
122
|
+
list, inspect, and execute for form controls and data surfaces. The tool set does
|
|
123
|
+
not change as components mount and unmount; each call reads the current
|
|
124
|
+
transport-neutral registries instead of inspecting or simulating the DOM.
|
|
125
|
+
|
|
126
|
+
Forms automatically join the Provider's control registry. An explicit Form
|
|
127
|
+
`interactionRegistry` still takes precedence. Pass the same data-surface
|
|
128
|
+
registry used by `DataTable` or `CollectionToolbar` when those mounted surfaces
|
|
129
|
+
should be discoverable:
|
|
130
|
+
|
|
131
|
+
```svelte
|
|
132
|
+
<script lang="ts">
|
|
133
|
+
import { createDataSurfaceRegistry } from '@happyvertical/smrt-ui/data';
|
|
134
|
+
import { Provider } from '@happyvertical/smrt-svelte';
|
|
135
|
+
|
|
136
|
+
const surfaces = createDataSurfaceRegistry();
|
|
137
|
+
</script>
|
|
138
|
+
|
|
139
|
+
<Provider webmcp={{ ui: { dataSurfaceRegistry: surfaces } }}>
|
|
140
|
+
<!-- pass {surfaces} to mounted data-surface components -->
|
|
141
|
+
{@render children()}
|
|
142
|
+
</Provider>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The default prefix is `smrt_ui_`. Configure `ui.prefix` when multiple Providers
|
|
146
|
+
must coexist in one document; the same prefix cannot be registered twice.
|
|
147
|
+
`ui: false` disables only the fixed UI adapter while leaving generated model
|
|
148
|
+
tools enabled. For compatibility, an object config that omits `ui` continues to
|
|
149
|
+
enable only generated model tools; use `webmcp={true}` or provide `ui: {}` to
|
|
150
|
+
enable the mounted-UI adapter.
|
|
151
|
+
|
|
152
|
+
Form commands always run with `source: 'agent'`. WebMCP input cannot assert
|
|
153
|
+
confirmation: staging is allowed by the registry policy, while apply, clear,
|
|
154
|
+
and undo require a separate human-confirmed path. Secret control values and
|
|
155
|
+
hidden data-surface columns are not serialized. Read responses are marked as
|
|
156
|
+
untrusted content. Bespoke `useWebMcpTool` and `<Form webmcp>` tools retain their
|
|
157
|
+
existing lifecycle and submit behavior.
|
|
158
|
+
|
|
95
159
|
### Form Components
|
|
96
160
|
|
|
97
161
|
```svelte
|
package/dist/Provider.svelte
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { createDataSurfaceRegistry } from '@happyvertical/smrt-ui/data';
|
|
3
|
+
import { createControlInteractionRegistry } from '@happyvertical/smrt-ui/forms';
|
|
2
4
|
import {
|
|
3
5
|
createI18nContext,
|
|
4
6
|
type I18nSnapshot,
|
|
5
7
|
setI18nContext,
|
|
6
8
|
} from '@happyvertical/smrt-ui/i18n';
|
|
7
|
-
import {
|
|
8
|
-
type RegisterWebMcpToolsOptions,
|
|
9
|
-
type SmrtWebClient,
|
|
10
|
-
type SmrtWebCollectionDefinition,
|
|
11
|
-
} from '@happyvertical/smrt-web';
|
|
12
9
|
import type { Snippet } from 'svelte';
|
|
13
10
|
import { onDestroy, untrack } from 'svelte';
|
|
14
11
|
import { logger } from './internal/logger.js';
|
|
@@ -21,6 +18,9 @@ import type {
|
|
|
21
18
|
} from './state/app-state.js';
|
|
22
19
|
import { createAppState } from './state/app-state.svelte.js';
|
|
23
20
|
import { setAppStateContext } from './state/context.js';
|
|
21
|
+
import type { WebMcpProviderConfig } from './web/webmcp-provider.js';
|
|
22
|
+
import { registerWebMcpUiTools } from './web/webmcp-ui.js';
|
|
23
|
+
import { setWebMcpUiContext } from './web/webmcp-ui-context.js';
|
|
24
24
|
|
|
25
25
|
interface Props {
|
|
26
26
|
/**
|
|
@@ -84,9 +84,9 @@ interface Props {
|
|
|
84
84
|
*/
|
|
85
85
|
i18n?: I18nSnapshot;
|
|
86
86
|
/**
|
|
87
|
-
* Opt in to
|
|
88
|
-
* WebMCP is feature-detected
|
|
89
|
-
* browsers that do not expose `document.modelContext`.
|
|
87
|
+
* Opt in to generated data tools and the fixed mounted-UI tool adapter for
|
|
88
|
+
* this browser surface. WebMCP is feature-detected, so this is safe during
|
|
89
|
+
* SSR and on browsers that do not expose `document.modelContext`.
|
|
90
90
|
*/
|
|
91
91
|
webmcp?: boolean | WebMcpProviderConfig;
|
|
92
92
|
/**
|
|
@@ -95,15 +95,6 @@ interface Props {
|
|
|
95
95
|
children: Snippet;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
export interface WebMcpProviderConfig {
|
|
99
|
-
definitions?: SmrtWebCollectionDefinition[];
|
|
100
|
-
client?: SmrtWebClient;
|
|
101
|
-
basePath?: string;
|
|
102
|
-
fetchFn?: typeof fetch;
|
|
103
|
-
scope?: string;
|
|
104
|
-
filter?: RegisterWebMcpToolsOptions['filter'];
|
|
105
|
-
}
|
|
106
|
-
|
|
107
98
|
const {
|
|
108
99
|
mode,
|
|
109
100
|
autoEnableSmrt = true,
|
|
@@ -135,6 +126,35 @@ const appState = createAppState({
|
|
|
135
126
|
},
|
|
136
127
|
});
|
|
137
128
|
|
|
129
|
+
const localControlRegistry = createControlInteractionRegistry();
|
|
130
|
+
const localDataSurfaceRegistry = createDataSurfaceRegistry();
|
|
131
|
+
const webMcpConfig = $derived(typeof webmcp === 'object' ? webmcp : undefined);
|
|
132
|
+
const webMcpUiConfig = $derived(
|
|
133
|
+
webmcp === true
|
|
134
|
+
? {}
|
|
135
|
+
: typeof webMcpConfig?.ui === 'object'
|
|
136
|
+
? webMcpConfig.ui
|
|
137
|
+
: undefined,
|
|
138
|
+
);
|
|
139
|
+
const resolvedControlRegistry = $derived(
|
|
140
|
+
webMcpUiConfig?.controlRegistry ?? localControlRegistry,
|
|
141
|
+
);
|
|
142
|
+
const resolvedDataSurfaceRegistry = $derived(
|
|
143
|
+
webMcpUiConfig?.dataSurfaceRegistry ?? localDataSurfaceRegistry,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
setWebMcpUiContext({
|
|
147
|
+
get enabled() {
|
|
148
|
+
return webMcpUiConfig !== undefined;
|
|
149
|
+
},
|
|
150
|
+
get controlRegistry() {
|
|
151
|
+
return resolvedControlRegistry;
|
|
152
|
+
},
|
|
153
|
+
get dataSurfaceRegistry() {
|
|
154
|
+
return resolvedDataSurfaceRegistry;
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
138
158
|
$effect(() => {
|
|
139
159
|
const currentPreferences = untrack(() => appState.state.session.preferences);
|
|
140
160
|
if (currentPreferences.autoEnableSmrt === autoEnableSmrt) {
|
|
@@ -149,6 +169,21 @@ $effect(() => {
|
|
|
149
169
|
});
|
|
150
170
|
});
|
|
151
171
|
|
|
172
|
+
// Register one fixed UI tool set. Mounted components change registry contents,
|
|
173
|
+
// not the document-level WebMCP tool set.
|
|
174
|
+
$effect(() => {
|
|
175
|
+
if (typeof window === 'undefined' || !webmcp || !webMcpUiConfig) return;
|
|
176
|
+
try {
|
|
177
|
+
return registerWebMcpUiTools({
|
|
178
|
+
controlRegistry: resolvedControlRegistry,
|
|
179
|
+
dataSurfaceRegistry: resolvedDataSurfaceRegistry,
|
|
180
|
+
...(webMcpUiConfig.prefix ? { prefix: webMcpUiConfig.prefix } : {}),
|
|
181
|
+
});
|
|
182
|
+
} catch (error) {
|
|
183
|
+
logger.warn('Provider: WebMCP UI tool registration rejected', { error });
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
152
187
|
// Keep generated data-plane tools scoped to this Provider. The registrar
|
|
153
188
|
// feature-detects WebMCP itself; effects do not run during SSR.
|
|
154
189
|
$effect(() => {
|
|
@@ -160,16 +195,38 @@ $effect(() => {
|
|
|
160
195
|
|
|
161
196
|
// Keep the data-plane engine out of applications that do not opt in. The
|
|
162
197
|
// dynamic import also means SSR never evaluates browser-only engine code.
|
|
163
|
-
void import('@happyvertical/smrt-web')
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
198
|
+
void import('@happyvertical/smrt-web')
|
|
199
|
+
.then(({ registerWebMcpTools }) => {
|
|
200
|
+
if (cancelled) return;
|
|
201
|
+
const registration = registerWebMcpTools(config.definitions ?? [], {
|
|
202
|
+
client: config.client,
|
|
203
|
+
basePath: config.basePath,
|
|
204
|
+
fetchFn: config.fetchFn,
|
|
205
|
+
scope: config.scope,
|
|
206
|
+
filter: config.filter,
|
|
207
|
+
filterTool: config.filterTool,
|
|
208
|
+
resolveFetchers: config.resolveFetchers,
|
|
209
|
+
resolveToolFetchers: config.resolveToolFetchers,
|
|
210
|
+
effects: config.effects,
|
|
211
|
+
namespace: config.namespace,
|
|
212
|
+
maxTools: config.maxTools,
|
|
213
|
+
});
|
|
214
|
+
dispose = registration;
|
|
215
|
+
void registration.ready.catch((error) => {
|
|
216
|
+
if (!cancelled) {
|
|
217
|
+
logger.warn('Provider: WebMCP data tool registration rejected', {
|
|
218
|
+
error,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
})
|
|
223
|
+
.catch((error) => {
|
|
224
|
+
if (!cancelled) {
|
|
225
|
+
logger.warn('Provider: WebMCP data tool registration rejected', {
|
|
226
|
+
error,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
171
229
|
});
|
|
172
|
-
});
|
|
173
230
|
|
|
174
231
|
return () => {
|
|
175
232
|
cancelled = true;
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import { type I18nSnapshot } from '@happyvertical/smrt-ui/i18n';
|
|
2
|
-
import { type RegisterWebMcpToolsOptions, type SmrtWebClient, type SmrtWebCollectionDefinition } from '@happyvertical/smrt-web';
|
|
3
2
|
import type { Snippet } from 'svelte';
|
|
4
3
|
import type { AIConfig, AILoadingState, AppMode, SocketConfig, User } from './state/app-state.js';
|
|
5
|
-
|
|
6
|
-
definitions?: SmrtWebCollectionDefinition[];
|
|
7
|
-
client?: SmrtWebClient;
|
|
8
|
-
basePath?: string;
|
|
9
|
-
fetchFn?: typeof fetch;
|
|
10
|
-
scope?: string;
|
|
11
|
-
filter?: RegisterWebMcpToolsOptions['filter'];
|
|
12
|
-
}
|
|
4
|
+
import type { WebMcpProviderConfig } from './web/webmcp-provider.js';
|
|
13
5
|
interface Props {
|
|
14
6
|
/**
|
|
15
7
|
* Initial mode: 'default' | 'smrt'
|
|
@@ -72,9 +64,9 @@ interface Props {
|
|
|
72
64
|
*/
|
|
73
65
|
i18n?: I18nSnapshot;
|
|
74
66
|
/**
|
|
75
|
-
* Opt in to
|
|
76
|
-
* WebMCP is feature-detected
|
|
77
|
-
* browsers that do not expose `document.modelContext`.
|
|
67
|
+
* Opt in to generated data tools and the fixed mounted-UI tool adapter for
|
|
68
|
+
* this browser surface. WebMCP is feature-detected, so this is safe during
|
|
69
|
+
* SSR and on browsers that do not expose `document.modelContext`.
|
|
78
70
|
*/
|
|
79
71
|
webmcp?: boolean | WebMcpProviderConfig;
|
|
80
72
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Provider.svelte.d.ts","sourceRoot":"","sources":["../src/Provider.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Provider.svelte.d.ts","sourceRoot":"","sources":["../src/Provider.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,YAAY,EAElB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGtC,OAAO,KAAK,EACV,QAAQ,EACR,cAAc,EACd,OAAO,EACP,YAAY,EACZ,IAAI,EACL,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAKrE,UAAU,KAAK;IACb;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;;;;;;;;;;;;;OAeG;IACH,EAAE,CAAC,EAAE,QAAQ,CAAC;IACd;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC;;OAEG;IACH,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IACpD;;;;;OAKG;IACH,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAC;IACxC;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAC;CACnB;AAiPD,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Provider from '../Provider.svelte';
|
|
3
|
+
import type { WebMcpProviderConfig } from '../web/webmcp-provider.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
webmcp: WebMcpProviderConfig;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { webmcp }: Props = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<Provider {webmcp}>
|
|
13
|
+
<span></span>
|
|
14
|
+
</Provider>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { WebMcpProviderConfig } from '../web/webmcp-provider.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
webmcp: WebMcpProviderConfig;
|
|
4
|
+
}
|
|
5
|
+
declare const ProviderWebmcpHarness: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type ProviderWebmcpHarness = ReturnType<typeof ProviderWebmcpHarness>;
|
|
7
|
+
export default ProviderWebmcpHarness;
|
|
8
|
+
//# sourceMappingURL=provider-webmcp-harness.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-webmcp-harness.svelte.d.ts","sourceRoot":"","sources":["../../src/__tests__/provider-webmcp-harness.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAGtE,UAAU,KAAK;IACb,MAAM,EAAE,oBAAoB,CAAC;CAC9B;AAeD,QAAA,MAAM,qBAAqB,2CAAwC,CAAC;AACpE,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACtE,eAAe,qBAAqB,CAAC"}
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
setFormContext,
|
|
20
20
|
} from '../../state/form-context.js';
|
|
21
21
|
import { useWebMcpTool } from '../../web/webmcp.svelte.js';
|
|
22
|
+
import { tryGetWebMcpUiContext } from '../../web/webmcp-ui-context.js';
|
|
22
23
|
import type { LLMModelId, STTAdapterType } from './types.js';
|
|
23
24
|
|
|
24
25
|
const { t } = useI18n();
|
|
@@ -80,11 +81,21 @@ const stt = useSTT();
|
|
|
80
81
|
const instanceId = $props.id();
|
|
81
82
|
const generatedFormId = `smrt-form-${instanceId}`;
|
|
82
83
|
const localInteractionRegistry = createControlInteractionRegistry();
|
|
84
|
+
const providerWebMcpUi = tryGetWebMcpUiContext();
|
|
83
85
|
const resolvedFormId = $derived(formId ?? id ?? name ?? generatedFormId);
|
|
84
86
|
const resolvedInteractionRegistry = $derived(
|
|
85
|
-
interactionRegistry ??
|
|
87
|
+
interactionRegistry ??
|
|
88
|
+
(providerWebMcpUi?.enabled
|
|
89
|
+
? providerWebMcpUi.controlRegistry
|
|
90
|
+
: undefined) ??
|
|
91
|
+
localInteractionRegistry,
|
|
86
92
|
);
|
|
87
|
-
const interactionDisposers = new Map<
|
|
93
|
+
const interactionDisposers = new Map<
|
|
94
|
+
string,
|
|
95
|
+
{ field: FieldDefinition; dispose: () => void }
|
|
96
|
+
>();
|
|
97
|
+
let activeInteractionRegistry: ControlInteractionRegistry | undefined;
|
|
98
|
+
let activeInteractionFormId: string | undefined;
|
|
88
99
|
|
|
89
100
|
setControlInteractionContext({
|
|
90
101
|
get formId() {
|
|
@@ -97,7 +108,14 @@ setControlInteractionContext({
|
|
|
97
108
|
|
|
98
109
|
$effect(() => {
|
|
99
110
|
if (!oninteraction) return;
|
|
100
|
-
return resolvedInteractionRegistry.subscribe(
|
|
111
|
+
return resolvedInteractionRegistry.subscribe((event) => {
|
|
112
|
+
if (event.identity.formId !== resolvedFormId) return;
|
|
113
|
+
try {
|
|
114
|
+
oninteraction(event);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
logger.warn('Form: interaction observer failed', { error });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
101
119
|
});
|
|
102
120
|
|
|
103
121
|
// Internal state
|
|
@@ -132,47 +150,104 @@ function interactionKind(field: FieldDefinition): ControlKind {
|
|
|
132
150
|
}
|
|
133
151
|
}
|
|
134
152
|
|
|
153
|
+
function registerInteraction(
|
|
154
|
+
registry: ControlInteractionRegistry,
|
|
155
|
+
currentFormId: string,
|
|
156
|
+
field: FieldDefinition,
|
|
157
|
+
): (() => void) | undefined {
|
|
158
|
+
const identity = {
|
|
159
|
+
formId: currentFormId,
|
|
160
|
+
controlId: field.controlId ?? field.name,
|
|
161
|
+
};
|
|
162
|
+
if (registry.get(identity)) {
|
|
163
|
+
logger.warn('Form: duplicate interaction identity rejected', { identity });
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
return registry.register({
|
|
167
|
+
identity,
|
|
168
|
+
metadata: {
|
|
169
|
+
kind: interactionKind(field),
|
|
170
|
+
label: field.label,
|
|
171
|
+
description: field.description,
|
|
172
|
+
sensitivity: field.sensitivity ?? 'public',
|
|
173
|
+
readable: field.readable,
|
|
174
|
+
writable: field.writable,
|
|
175
|
+
constraints: field.constraints,
|
|
176
|
+
options: field.options,
|
|
177
|
+
unit: field.unit,
|
|
178
|
+
},
|
|
179
|
+
getValue: field.getValue,
|
|
180
|
+
setValue: field.setValue,
|
|
181
|
+
clear: field.clear ?? (() => field.setValue('')),
|
|
182
|
+
focus: field.focus,
|
|
183
|
+
reveal: field.reveal,
|
|
184
|
+
highlight: field.highlight,
|
|
185
|
+
validate: field.validate,
|
|
186
|
+
getState: field.getState,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let interactionRegistryRevision = $state(0);
|
|
191
|
+
|
|
192
|
+
$effect(() => {
|
|
193
|
+
const registry = resolvedInteractionRegistry;
|
|
194
|
+
return registry.subscribe((event) => {
|
|
195
|
+
if (event.type === 'registered' || event.type === 'unregistered') {
|
|
196
|
+
interactionRegistryRevision += 1;
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
$effect(() => {
|
|
202
|
+
const registry = resolvedInteractionRegistry;
|
|
203
|
+
const currentFormId = resolvedFormId;
|
|
204
|
+
const currentFields = new Map(fields);
|
|
205
|
+
void interactionRegistryRevision;
|
|
206
|
+
if (
|
|
207
|
+
activeInteractionRegistry !== registry ||
|
|
208
|
+
activeInteractionFormId !== currentFormId
|
|
209
|
+
) {
|
|
210
|
+
for (const registration of interactionDisposers.values()) {
|
|
211
|
+
registration.dispose();
|
|
212
|
+
}
|
|
213
|
+
interactionDisposers.clear();
|
|
214
|
+
activeInteractionRegistry = registry;
|
|
215
|
+
activeInteractionFormId = currentFormId;
|
|
216
|
+
}
|
|
217
|
+
for (const [name, registration] of interactionDisposers) {
|
|
218
|
+
const identity = {
|
|
219
|
+
formId: currentFormId,
|
|
220
|
+
controlId: registration.field.controlId ?? registration.field.name,
|
|
221
|
+
};
|
|
222
|
+
if (
|
|
223
|
+
currentFields.get(name) !== registration.field ||
|
|
224
|
+
!registry.get(identity)
|
|
225
|
+
) {
|
|
226
|
+
registration.dispose();
|
|
227
|
+
interactionDisposers.delete(name);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
for (const [name, field] of currentFields) {
|
|
231
|
+
if (interactionDisposers.has(name)) continue;
|
|
232
|
+
const dispose = registerInteraction(registry, currentFormId, field);
|
|
233
|
+
if (!dispose) continue;
|
|
234
|
+
interactionDisposers.set(name, {
|
|
235
|
+
field,
|
|
236
|
+
dispose,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
135
241
|
// Create form context
|
|
136
242
|
const formContext: SMRTFormContext = {
|
|
137
243
|
get mode() {
|
|
138
244
|
return app.state.mode === 'smrt' ? 'smrt' : 'default';
|
|
139
245
|
},
|
|
140
246
|
registerField(field: FieldDefinition) {
|
|
141
|
-
interactionDisposers.get(field.name)?.();
|
|
142
247
|
fields.set(field.name, field);
|
|
143
248
|
fields = new Map(fields); // Trigger reactivity
|
|
144
|
-
interactionDisposers.set(
|
|
145
|
-
field.name,
|
|
146
|
-
resolvedInteractionRegistry.register({
|
|
147
|
-
identity: {
|
|
148
|
-
formId: resolvedFormId,
|
|
149
|
-
controlId: field.controlId ?? field.name,
|
|
150
|
-
},
|
|
151
|
-
metadata: {
|
|
152
|
-
kind: interactionKind(field),
|
|
153
|
-
label: field.label,
|
|
154
|
-
description: field.description,
|
|
155
|
-
sensitivity: field.sensitivity ?? 'public',
|
|
156
|
-
readable: field.readable,
|
|
157
|
-
writable: field.writable,
|
|
158
|
-
constraints: field.constraints,
|
|
159
|
-
options: field.options,
|
|
160
|
-
unit: field.unit,
|
|
161
|
-
},
|
|
162
|
-
getValue: field.getValue,
|
|
163
|
-
setValue: field.setValue,
|
|
164
|
-
clear: field.clear ?? (() => field.setValue('')),
|
|
165
|
-
focus: field.focus,
|
|
166
|
-
reveal: field.reveal,
|
|
167
|
-
highlight: field.highlight,
|
|
168
|
-
validate: field.validate,
|
|
169
|
-
getState: field.getState,
|
|
170
|
-
}),
|
|
171
|
-
);
|
|
172
249
|
},
|
|
173
250
|
unregisterField(name: string) {
|
|
174
|
-
interactionDisposers.get(name)?.();
|
|
175
|
-
interactionDisposers.delete(name);
|
|
176
251
|
fields.delete(name);
|
|
177
252
|
fields = new Map(fields);
|
|
178
253
|
},
|
|
@@ -762,7 +837,9 @@ onDestroy(() => {
|
|
|
762
837
|
clearTimeout(silenceTimer);
|
|
763
838
|
}
|
|
764
839
|
stopAudioLevelMonitoring();
|
|
765
|
-
for (const
|
|
840
|
+
for (const registration of interactionDisposers.values()) {
|
|
841
|
+
registration.dispose();
|
|
842
|
+
}
|
|
766
843
|
interactionDisposers.clear();
|
|
767
844
|
});
|
|
768
845
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Form.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Form.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAIhC,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"Form.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Form.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAIhC,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAatC,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG7D,MAAM,WAAW,KAAK;IACpB,oBAAoB;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,8BAA8B;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,oCAAoC;IACpC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iDAAiD;IACjD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uBAAuB;IACvB,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,UAAU,CAAC;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,kDAAkD;IAClD,MAAM,CAAC,EAAE,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mBAAmB,CAAC,EAAE,0BAA0B,CAAC;IACjD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,CAAC;IACzD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAs4BD,QAAA,MAAM,IAAI;uBAhFe,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;kCAQZ,0BAA0B;MAwEZ,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
|
|
@@ -27,7 +27,7 @@ describe('Form WebMCP submit intent', () => {
|
|
|
27
27
|
it('registers a field-derived tool and dispatches validation + submit', async () => {
|
|
28
28
|
const registered = [];
|
|
29
29
|
document.modelContext = {
|
|
30
|
-
registerTool(tool) {
|
|
30
|
+
async registerTool(tool) {
|
|
31
31
|
registered.push(tool);
|
|
32
32
|
},
|
|
33
33
|
};
|
|
@@ -54,7 +54,7 @@ describe('Form WebMCP submit intent', () => {
|
|
|
54
54
|
it('publishes required and numeric constraints and validates them before submit', async () => {
|
|
55
55
|
const registered = [];
|
|
56
56
|
document.modelContext = {
|
|
57
|
-
registerTool(tool) {
|
|
57
|
+
async registerTool(tool) {
|
|
58
58
|
registered.push(tool);
|
|
59
59
|
},
|
|
60
60
|
};
|
|
@@ -94,7 +94,7 @@ describe('Form WebMCP submit intent', () => {
|
|
|
94
94
|
it('publishes and accepts structured measurement, date range, and address fields', async () => {
|
|
95
95
|
const registered = [];
|
|
96
96
|
document.modelContext = {
|
|
97
|
-
registerTool(tool) {
|
|
97
|
+
async registerTool(tool) {
|
|
98
98
|
registered.push(tool);
|
|
99
99
|
},
|
|
100
100
|
};
|
|
@@ -154,7 +154,7 @@ describe('Form WebMCP submit intent', () => {
|
|
|
154
154
|
it('limits the address schema and payload to configured fields', async () => {
|
|
155
155
|
const registered = [];
|
|
156
156
|
document.modelContext = {
|
|
157
|
-
registerTool(tool) {
|
|
157
|
+
async registerTool(tool) {
|
|
158
158
|
registered.push(tool);
|
|
159
159
|
},
|
|
160
160
|
};
|
|
@@ -190,7 +190,7 @@ describe('Form WebMCP submit intent', () => {
|
|
|
190
190
|
it('allows partial payloads for optional structured fields', async () => {
|
|
191
191
|
const registered = [];
|
|
192
192
|
document.modelContext = {
|
|
193
|
-
registerTool(tool) {
|
|
193
|
+
async registerTool(tool) {
|
|
194
194
|
registered.push(tool);
|
|
195
195
|
},
|
|
196
196
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -18,4 +18,7 @@ export * from './hooks/index.js';
|
|
|
18
18
|
export { default as Provider } from './Provider.svelte';
|
|
19
19
|
export * from './state/index.js';
|
|
20
20
|
export { useWebMcpTool, type WebMcpToolSpec } from './web/webmcp.svelte.js';
|
|
21
|
+
export type { WebMcpProviderConfig, WebMcpUiProviderConfig, } from './web/webmcp-provider.js';
|
|
22
|
+
export { type RegisterWebMcpUiToolsOptions, registerWebMcpUiTools, } from './web/webmcp-ui.js';
|
|
23
|
+
export { useWebMcpUi, type WebMcpUiContext, } from './web/webmcp-ui-context.js';
|
|
21
24
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AAGH,cAAc,6BAA6B,CAAC;AAE5C,cAAc,8BAA8B,CAAC;AAE7C,cAAc,mBAAmB,CAAC;AAElC,cAAc,kBAAkB,CAAC;AAEjC,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAExD,cAAc,kBAAkB,CAAC;AAEjC,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AAGH,cAAc,6BAA6B,CAAC;AAE5C,cAAc,8BAA8B,CAAC;AAE7C,cAAc,mBAAmB,CAAC;AAElC,cAAc,kBAAkB,CAAC;AAEjC,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAExD,cAAc,kBAAkB,CAAC;AAEjC,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC5E,YAAY,EACV,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,KAAK,4BAA4B,EACjC,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,WAAW,EACX,KAAK,eAAe,GACrB,MAAM,4BAA4B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -26,3 +26,5 @@ export { default as Provider } from './Provider.svelte';
|
|
|
26
26
|
export * from './state/index.js';
|
|
27
27
|
// Opt-in browser WebMCP lifecycle primitive.
|
|
28
28
|
export { useWebMcpTool } from './web/webmcp.svelte.js';
|
|
29
|
+
export { registerWebMcpUiTools, } from './web/webmcp-ui.js';
|
|
30
|
+
export { useWebMcpUi, } from './web/webmcp-ui-context.js';
|