@happyvertical/smrt-web 0.51.31 → 0.51.33

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 (47) hide show
  1. package/dist/__typechecks__/webmcp-tool-definition.d.ts +2 -0
  2. package/dist/__typechecks__/webmcp-tool-definition.d.ts.map +1 -0
  3. package/dist/ai.d.ts +330 -358
  4. package/dist/ai.d.ts.map +1 -0
  5. package/dist/capability-classification.d.ts +89 -0
  6. package/dist/capability-classification.d.ts.map +1 -0
  7. package/dist/capability.d.ts +191 -0
  8. package/dist/capability.d.ts.map +1 -0
  9. package/dist/data-query.d.ts +121 -0
  10. package/dist/data-query.d.ts.map +1 -0
  11. package/dist/data-surface-actions.d.ts +118 -0
  12. package/dist/data-surface-actions.d.ts.map +1 -0
  13. package/dist/durable-store.d.ts +88 -0
  14. package/dist/durable-store.d.ts.map +1 -0
  15. package/dist/index.d.ts +468 -2245
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/intents.d.ts +273 -300
  18. package/dist/intents.d.ts.map +1 -0
  19. package/dist/internal.d.ts +15 -0
  20. package/dist/internal.d.ts.map +1 -0
  21. package/dist/offline/durable-queue.d.ts +171 -0
  22. package/dist/offline/durable-queue.d.ts.map +1 -0
  23. package/dist/offline/engine.d.ts +359 -0
  24. package/dist/offline/engine.d.ts.map +1 -0
  25. package/dist/offline/leader.d.ts +57 -0
  26. package/dist/offline/leader.d.ts.map +1 -0
  27. package/dist/offline/types.d.ts +232 -0
  28. package/dist/offline/types.d.ts.map +1 -0
  29. package/dist/offline.d.ts +258 -0
  30. package/dist/offline.d.ts.map +1 -0
  31. package/dist/persistence/snapshot-store.d.ts +121 -0
  32. package/dist/persistence/snapshot-store.d.ts.map +1 -0
  33. package/dist/persistence.d.ts +157 -0
  34. package/dist/persistence.d.ts.map +1 -0
  35. package/dist/remote-query.d.ts +61 -0
  36. package/dist/remote-query.d.ts.map +1 -0
  37. package/dist/sse-client.d.ts +182 -0
  38. package/dist/sse-client.d.ts.map +1 -0
  39. package/dist/update-state/meta-store.d.ts +66 -0
  40. package/dist/update-state/meta-store.d.ts.map +1 -0
  41. package/dist/update-state.d.ts +115 -0
  42. package/dist/update-state.d.ts.map +1 -0
  43. package/dist/webmcp-tool-names.d.ts +106 -112
  44. package/dist/webmcp-tool-names.d.ts.map +1 -0
  45. package/dist/webmcp.d.ts +193 -653
  46. package/dist/webmcp.d.ts.map +1 -0
  47. package/package.json +4 -4
package/dist/webmcp.d.ts CHANGED
@@ -1,653 +1,193 @@
1
- /**
2
- * Register one declared view intent (#2588) against a mounted registry
3
- * binding, for as long as the returned disposer is not called.
4
- *
5
- * This is the ONLY way an intent reaches the browser, and it goes through
6
- * {@link registerWebMcpBespokeTool}, so an intent inherits the same
7
- * fail-closed `effects` exposure policy as a generated model tool. The
8
- * intent's classification was resolved at declaration time by the shared
9
- * rule, and the hints `compileViewIntentToolSpec` emits round-trip through
10
- * the bespoke registrar's own re-resolution unchanged.
11
- *
12
- * The `execute` registered here is constructed by
13
- * `compileViewIntentToolSpec` from `intent.target`. No author-supplied code
14
- * runs, and the only thing it can do is dispatch one browser registry
15
- * command — the runtime half of the no-REST invariant.
16
- *
17
- * The intent's derived tool name is claimed against the document-global
18
- * tool-name lock (#2613), so an id whose flattened name collides with a
19
- * generated model tool or a fixed UI tool throws
20
- * {@link WebMcpToolNameCollisionError} here rather than silently losing the
21
- * tool at the host. `defineIntent`'s own intent-vs-intent check cannot see
22
- * either, because both depend on a runtime `namespace` / `prefix` the
23
- * declaration never sees.
24
- *
25
- * @throws {WebMcpToolNameCollisionError} when the derived name is already held.
26
- */
27
- export declare function registerViewIntent(intent: ViewIntent, binding: ViewIntentBinding, options?: RegisterWebMcpBespokeToolOptions): WebMcpRegistrationDisposer;
28
-
29
- /**
30
- * Register one hand-written browser tool through the same fail-closed effect
31
- * classification and `effects` exposure policy as {@link registerWebMcpTools}
32
- * (#2586). A tool with no `annotations`, or with annotations that leave its
33
- * effect undeclared, classifies destructive/non-idempotent/open-world — the
34
- * same default `actionSemantics` gives an undeclared custom model action —
35
- * and is excluded unless the policy allows `destructive`. `namespace` and
36
- * `maxTools` are out of scope for a bespoke tool; see
37
- * {@link RegisterWebMcpBespokeToolOptions}.
38
- *
39
- * The tool's name is also claimed against the
40
- * document-global tool-name lock before the browser sees it. A name a
41
- * generated model tool, a fixed UI tool, or another live bespoke tool /
42
- * intent already holds throws {@link WebMcpToolNameCollisionError}
43
- * SYNCHRONOUSLY, naming the colliding name and the owner that holds it.
44
- * Previously the host accepted the call and silently dropped the losing
45
- * registration. Disposing releases the name, so register/dispose/re-register
46
- * under one name still succeeds.
47
- *
48
- * @returns a disposer that deregisters the tool this call registered (a
49
- * no-op double-call). On a browser without WebMCP, or when policy excludes
50
- * the tool's effect, the call is a no-op and the disposer is inert.
51
- * @throws {WebMcpToolNameCollisionError} when the name is already held.
52
- */
53
- export declare function registerWebMcpBespokeTool(spec: WebMcpBespokeToolSpec, options?: RegisterWebMcpBespokeToolOptions): WebMcpRegistrationDisposer;
54
-
55
- export declare interface RegisterWebMcpBespokeToolOptions {
56
- /**
57
- * Allowed effects. Omitted means read-only exposure — the same default as
58
- * {@link registerWebMcpTools}. `namespace` and `maxTools` deliberately do
59
- * not apply to a bespoke tool (#2586): a component author already chose a
60
- * stable name, and counting one intent against a shared budget could make
61
- * an unrelated generated tool set fail to register.
62
- */
63
- effects?: readonly WebMcpToolEffect[];
64
- /**
65
- * Which path this registration belongs to, used ONLY to label its
66
- * tool-name reservation (#2613) so a later collision names the right
67
- * source. Defaults to `bespoke`.
68
- *
69
- * A UI-framework binding that compiles a declared view intent itself and
70
- * registers it here — rather than through {@link registerViewIntent} —
71
- * must pass `'intent'`. `useViewIntent` in `@happyvertical/smrt-svelte`
72
- * does exactly that, because reusing `useWebMcpTool`'s single WebMCP
73
- * lifecycle is a documented invariant of that package; without this it
74
- * would be the only shipped intent path and every intent collision would
75
- * blame a `useWebMcpTool` call that does not exist.
76
- *
77
- * Purely a diagnostic label: it grants no capability, narrows no policy,
78
- * and changes nothing about how the tool registers or executes.
79
- */
80
- owner?: Extract<WebMcpToolNameOwner, 'intent' | 'bespoke'>;
81
- }
82
-
83
- /**
84
- * Register every collection's generated tool descriptors with WebMCP.
85
- *
86
- * Every selected tool name is claimed against the document-global tool-name
87
- * lock (#2613) before the first browser registration, so a collision with a
88
- * UI-layer or bespoke tool throws {@link WebMcpToolNameCollisionError} with
89
- * the offending name and its current owner instead of reaching the host.
90
- *
91
- * @returns a disposer that deregisters all tools this call registered. On a
92
- * browser without WebMCP the call is a no-op and the disposer is inert.
93
- * @throws {WebMcpToolNameCollisionError} when a selected name is already held.
94
- */
95
- export declare function registerWebMcpTools(definitions: readonly WebMcpRegistrationDefinition[], options?: RegisterWebMcpToolsOptions): WebMcpRegistrationDisposer;
96
-
97
- export declare interface RegisterWebMcpToolsOptions extends WebMcpExposurePolicy {
98
- /** REST base path for the fetchers (default `/api/v1`). */
99
- basePath?: string;
100
- /** Injectable fetch (tests / SSR-safe wrappers). */
101
- fetchFn?: typeof fetch;
102
- /** Shared smrt-web cache handle used by page collections. */
103
- client?: SmrtWebClient;
104
- /** Optional cache scope matching the page collection's scope. */
105
- scope?: string;
106
- /**
107
- * Override how a definition's CRUD fetchers are built. Defaults to
108
- * {@link createDefinitionFetchers}; the primary seam for testing `execute`
109
- * without a live server.
110
- */
111
- resolveFetchers?: (definition: SmrtWebCollectionDefinition) => SmrtCrudFetchers;
112
- /**
113
- * Override direct REST fetchers for a canonical tool-only definition.
114
- * Fetchers are optional because a get-only or custom-action-only model does
115
- * not have a list or create route.
116
- */
117
- resolveToolFetchers?: (definition: WebMcpToolDefinition) => Partial<SmrtCrudFetchers>;
118
- /** Predicate to include/exclude individual tools (e.g. reads-only surfaces). */
119
- filter?: (definition: SmrtWebCollectionDefinition, descriptor: NonNullable<SmrtWebCollectionDefinition['toolDescriptors']>[number]) => boolean;
120
- /** Predicate for canonical per-tool definitions. */
121
- filterTool?: (definition: WebMcpToolDefinition) => boolean;
122
- }
123
-
124
- /**
125
- * Reserve `names` for `owner` against the document's WebMCP registry.
126
- *
127
- * Reservation is ALL-OR-NOTHING: if any name is already held — by this path
128
- * or another — nothing is reserved and a {@link WebMcpToolNameCollisionError}
129
- * naming the colliding name and its current owner is thrown synchronously,
130
- * before any tool reaches `document.modelContext`. Duplicates within `names`
131
- * itself collide the same way.
132
- *
133
- * Off-WebMCP (no document) the call is a no-op returning an inert
134
- * reservation, matching every registrar's own no-op guarantee.
135
- *
136
- * A reservation releases only the names it took: `release()` never drops a
137
- * name a later reservation has since acquired, so a dispose that lands after
138
- * a same-name re-registration cannot revoke the new holder's claim.
139
- */
140
- export declare function reserveWebMcpToolNames(names: readonly string[], owner: WebMcpToolNameOwner, options?: ReserveWebMcpToolNamesOptions): WebMcpToolNameReservation;
141
-
142
- declare interface ReserveWebMcpToolNamesOptions {
143
- /**
144
- * The document whose `modelContext` the names are registered against.
145
- * Defaults to `globalThis.document`. Registrars that accept an injectable
146
- * document (tests, non-window hosts) must pass the SAME object they read
147
- * `modelContext` from, or their reservations land in a different table.
148
- */
149
- document?: unknown;
150
- }
151
-
152
- /**
153
- * The per-collection CRUD surface of the generated REST client
154
- * (`createClient(basePath).<collection>` from `@happyvertical/smrt-virt-client`).
155
- *
156
- * Return types are `unknown` on purpose: generated fetchers resolve with
157
- * whatever the server sent, so this package normalizes and validates payloads
158
- * centrally — see {@link unwrapListResult} / {@link unwrapItemResult}.
159
- */
160
- declare interface SmrtCrudFetchers {
161
- list(params?: Record<string, unknown>): Promise<unknown>;
162
- get?(id: string): Promise<unknown>;
163
- create(data: Record<string, unknown>): Promise<unknown>;
164
- update?(id: string, data: Record<string, unknown>): Promise<unknown>;
165
- delete?(id: string): Promise<unknown>;
166
- /** Invoke a generated custom action route. */
167
- custom?(action: string, args: Record<string, unknown>, route?: SmrtWebToolRouteDescriptor): Promise<unknown>;
168
- }
169
-
170
- /**
171
- * Opaque handle to the shared client cache / request-dedup layer. Create one
172
- * with {@link createSmrtWebClient} and pass the SAME instance to every
173
- * collection that should share a cache and deduplicate in-flight requests.
174
- *
175
- * The engine (currently a TanStack Query client) is intentionally hidden behind
176
- * this brand so it stays swappable — do not depend on its concrete shape.
177
- */
178
- declare interface SmrtWebClient {
179
- /** Phantom brand — this handle wraps the hidden client-cache engine. */
180
- readonly __smrtWebClient: 'SmrtWebClient';
181
- }
182
-
183
- declare interface SmrtWebCollectionDefinition<TData extends object = object> {
184
- /** REST collection name (e.g. `products`). */
185
- name: string;
186
- /**
187
- * Canonical qualified model identity (e.g.
188
- * `@happyvertical/smrt-products:Product`). Generated definitions always
189
- * provide it; optional here for source compatibility with manual literals
190
- * authored before policy-aware web collections.
191
- */
192
- objectRef?: string;
193
- /** Source class name (e.g. `Product`). */
194
- className: string;
195
- /** Path under the API base path (e.g. `/products`). */
196
- endpoint: string;
197
- /** Primary key field name (`id` for SmrtObject). */
198
- idField: string;
199
- /** CRUD + custom actions exposed by the api decorator config. */
200
- actions: string[];
201
- /**
202
- * WebMCP/MCP tool descriptors for the exposed actions (#1812). Optional so
203
- * hand-built definitions (older codegen, tests) still satisfy the type; a
204
- * missing value means "no WebMCP tools to register".
205
- */
206
- toolDescriptors?: WebToolDescriptor[];
207
- /** Persisted field metadata keyed by field name. */
208
- fields: Record<string, SmrtWebFieldDefinition>;
209
- /**
210
- * Manifest-derived relationship edges to sibling REST collections. Drives
211
- * relationship-derived cache invalidation: a settled mutation on this
212
- * collection invalidates the caches of the collections these edges name.
213
- * Optional so hand-built definitions (older codegen, tests) still satisfy the
214
- * type; a missing value means "no derived edges".
215
- */
216
- relationships?: SmrtWebRelationship[];
217
- /** Phantom row-type carrier — never present at runtime. */
218
- _row?: TData;
219
- }
220
-
221
- /**
222
- * Field metadata emitted per column by the `@happyvertical/smrt-virt-web`
223
- * virtual module (generated from the package manifest).
224
- */
225
- declare interface SmrtWebFieldDefinition {
226
- type: SmrtWebFieldType;
227
- required?: boolean;
228
- /** Whether the public field contract explicitly permits `null`. */
229
- nullable?: boolean;
230
- default?: unknown;
231
- /** Developer-authored `@field({ description })` (#2046) — end-user help seed. */
232
- description?: string;
233
- /** Static `@field({ ui })` hints (#2046). */
234
- ui?: SmrtWebFieldUIHints;
235
- }
236
-
237
- /**
238
- * The field types core's web-collection emission actually produces —
239
- * relationship pseudo-columns (`oneToMany`/`manyToMany`) and STI `meta`
240
- * internals never reach an emitted definition. Textual mirror of core's
241
- * `WebFieldType` (this package is deliberately smrt-dependency-free, so it
242
- * cannot import the source type); the four co-managed sites are core's
243
- * runtime type, the ambient virt-web d.ts, the physical `@smrt/web` d.ts,
244
- * and this mirror.
245
- */
246
- declare type SmrtWebFieldType = 'text' | 'decimal' | 'boolean' | 'integer' | 'datetime' | 'json' | 'foreignKey' | 'crossPackageRef';
247
-
248
- /** Static `@field({ ui })` hints (#2046) — the field-policy rail seed. */
249
- declare interface SmrtWebFieldUIHints {
250
- basic?: boolean;
251
- group?: string;
252
- order?: number;
253
- locked?: boolean;
254
- }
255
-
256
- /**
257
- * A manifest-derived edge from this collection to a sibling REST collection,
258
- * emitted by the `@happyvertical/smrt-virt-web` virtual module. When a mutation
259
- * on this collection settles, the caches of the collections named by these
260
- * edges are invalidated (relationship-derived invalidation, #1761), so a
261
- * dependent view refetches without any hand-wired cache key.
262
- *
263
- * SMRT-owned data — no client-engine (`@tanstack/*`) type appears here, so it
264
- * stays inside the engine-absorption boundary.
265
- */
266
- declare interface SmrtWebRelationship {
267
- /** The declaring field carrying the relationship (e.g. `groupId`, `items`). */
268
- field: string;
269
- /** The relationship kind, mirroring the manifest field type. */
270
- kind: SmrtWebRelationshipKind;
271
- /** REST collection name the edge resolves to (e.g. `ad_groups`). */
272
- relatedCollection: string;
273
- }
274
-
275
- /** The relationship kinds a generated web collection edge can describe. */
276
- declare type SmrtWebRelationshipKind = 'foreignKey' | 'crossPackageRef' | 'oneToMany' | 'manyToMany';
277
-
278
- declare interface SmrtWebToolRouteDescriptor {
279
- method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
280
- scope: 'item' | 'collection';
281
- /** Route segments below the collection endpoint; dynamic segments use `[x]`. */
282
- path: string[];
283
- /** Transport names rewritten by the tool schema (e.g. `actionId` → `id`). */
284
- parameterAliases?: Record<string, string>;
285
- /** The generated method accepts one `options` bag as its sole argument. */
286
- optionsBag?: boolean;
287
- }
288
-
289
- /**
290
- * A validated, frozen intent.
291
- *
292
- * `kind: 'intent'` and `id` are the exported identity a `smrt-playbooks`
293
- * step reference (`{ kind: 'intent', id }`) names, and this object is
294
- * directly assignable to that package's `PlaybookIntentRecord` seam —
295
- * `{ id, classification, planes }` — so {@link resolveViewIntent} can be
296
- * passed as its `intents` resolver unchanged.
297
- */
298
- declare interface ViewIntent {
299
- readonly kind: 'intent';
300
- readonly id: string;
301
- readonly description: string;
302
- readonly inputSchema: Record<string, unknown>;
303
- /** Resolved through the shared fail-closed rule at declaration time. */
304
- readonly classification: WebMcpCapabilityClassification;
305
- readonly target: ViewIntentTarget;
306
- /**
307
- * An intent moves mounted browser state, so it is browser-valid only. A
308
- * server-side agent reaches one through the #2446 command/ack bridge, which
309
- * a playbook must declare explicitly.
310
- */
311
- readonly planes: readonly ['browser'];
312
- }
313
-
314
- /** The mounted identity a binding supplies for an intent's lifetime. */
315
- declare type ViewIntentBinding = {
316
- registry: 'control';
317
- registryPort: ViewIntentControlRegistryPort;
318
- identity: ViewIntentControlIdentity;
319
- } | {
320
- registry: 'dataSurface';
321
- registryPort: ViewIntentDataSurfaceRegistryPort;
322
- identity: ViewIntentDataSurfaceIdentity;
323
- };
324
-
325
- /**
326
- * Control commands a view intent may dispatch. Structurally mirrors
327
- * `ControlCommandAction` in `@happyvertical/smrt-ui/forms`; this package
328
- * cannot import that one (see AGENTS.md "No inter-smrt dependencies").
329
- */
330
- declare type ViewIntentControlAction = 'focus' | 'reveal' | 'highlight' | 'explain' | 'validate' | 'stage' | 'apply' | 'discard' | 'clear' | 'undo';
331
-
332
- /** A mounted control's full registry key, subject included. */
333
- declare interface ViewIntentControlIdentity {
334
- formId: string;
335
- controlId: string;
336
- subject?: ViewIntentSubject;
337
- }
338
-
339
- /**
340
- * The `ControlInteractionRegistry` surface an intent uses. Declared
341
- * structurally so this package takes no dependency on
342
- * `@happyvertical/smrt-ui`; the real registry satisfies it.
343
- */
344
- declare interface ViewIntentControlRegistryPort {
345
- execute(command: {
346
- action: ViewIntentControlAction;
347
- identity: ViewIntentControlIdentity;
348
- value?: unknown;
349
- durationMs?: number;
350
- revision?: number;
351
- }, context?: {
352
- source: 'agent';
353
- }): Promise<{
354
- ok: boolean;
355
- reason?: string;
356
- }>;
357
- }
358
-
359
- /**
360
- * A control-registry target: the intent dispatches exactly one
361
- * `ControlInteractionRegistry` command against a mounted control.
362
- *
363
- * `formId`/`controlId` are the statically declared half of the identity. A
364
- * binding supplies the mounted identity and must MATCH anything declared
365
- * here — a declaration is authority over a binding, never the other way
366
- * round.
367
- */
368
- declare interface ViewIntentControlTarget {
369
- registry: 'control';
370
- action: ViewIntentControlAction;
371
- formId?: string;
372
- controlId?: string;
373
- }
374
-
375
- /** A mounted data surface's full registry key, subject included. */
376
- declare interface ViewIntentDataSurfaceIdentity {
377
- surfaceId: string;
378
- kind: ViewIntentDataSurfaceKind;
379
- subject?: ViewIntentSubject;
380
- }
381
-
382
- /** Mirrors `DataSurfaceIdentity['kind']` in `@happyvertical/smrt-ui/data`. */
383
- declare type ViewIntentDataSurfaceKind = 'table' | 'list' | 'report' | 'custom';
384
-
385
- /** The `DataSurfaceRegistry` surface an intent uses. */
386
- declare interface ViewIntentDataSurfaceRegistryPort {
387
- inspect(identity: ViewIntentDataSurfaceIdentity): {
388
- revision: number;
389
- } | undefined;
390
- execute(command: {
391
- version: 1;
392
- commandId: string;
393
- identity: ViewIntentDataSurfaceIdentity;
394
- expectedRevision: number;
395
- controlId: string;
396
- payload?: unknown;
397
- }): Promise<{
398
- ok: boolean;
399
- revision?: number;
400
- reason?: string;
401
- }>;
402
- }
403
-
404
- /**
405
- * A data-surface target: the intent dispatches one
406
- * `DataSurfaceVisibleCommand` — a browser-visible state transition, never a
407
- * server-side query or mutation.
408
- */
409
- declare interface ViewIntentDataSurfaceTarget {
410
- registry: 'dataSurface';
411
- /** The visible-command `controlId` the mounted surface implements. */
412
- controlId: string;
413
- surfaceId?: string;
414
- kind?: ViewIntentDataSurfaceKind;
415
- }
416
-
417
- /**
418
- * The optional record a registry identity is qualified by. Rich forms use it
419
- * to tell apart controls that share a `formId`/`controlId` across records.
420
- * Structurally mirrors `ControlIdentity['subject']` /
421
- * `DataSurfaceIdentity['subject']` in `@happyvertical/smrt-ui`.
422
- */
423
- declare interface ViewIntentSubject {
424
- type: string;
425
- id: string;
426
- label?: string;
427
- }
428
-
429
- declare type ViewIntentTarget = ViewIntentControlTarget | ViewIntentDataSurfaceTarget;
430
-
431
- /**
432
- * A hand-written browser tool from application code — not generated from a
433
- * `@smrt()` model. Structurally identical to the WebMCP `registerTool` input;
434
- * kept as a separate type so this framework-agnostic module never depends on
435
- * a UI layer's tool-spec type.
436
- */
437
- export declare interface WebMcpBespokeToolSpec {
438
- name: string;
439
- description: string;
440
- inputSchema: Record<string, unknown>;
441
- annotations?: {
442
- readOnlyHint?: boolean;
443
- destructiveHint?: boolean;
444
- idempotentHint?: boolean;
445
- openWorldHint?: boolean;
446
- untrustedContentHint?: boolean;
447
- };
448
- execute: (args: Record<string, unknown>, options?: WebMcpToolExecutionOptions) => string | Promise<string>;
449
- }
450
-
451
- /** A fully resolved classification. */
452
- declare interface WebMcpCapabilityClassification {
453
- effect: WebMcpToolEffect;
454
- /**
455
- * Derived, never declared: every non-read effect is annotated destructive
456
- * to the browser, so a `write` capability cannot claim the MCP
457
- * additive-only guarantee.
458
- */
459
- destructive: boolean;
460
- idempotent: boolean;
461
- openWorld: boolean;
462
- }
463
-
464
- export declare interface WebMcpExposurePolicy {
465
- /** Allowed effects. Omitted means read-only exposure. */
466
- effects?: readonly WebMcpToolEffect[];
467
- /** Prefix every registered tool name with `<namespace>_`. */
468
- namespace?: string;
469
- /** Optional maximum tools registered by one call. */
470
- maxTools?: number;
471
- }
472
-
473
- /** Accepted legacy collection definitions and canonical per-tool definitions. */
474
- export declare type WebMcpRegistrationDefinition = SmrtWebCollectionDefinition | WebMcpToolDefinition;
475
-
476
- /** Disposes a registration and exposes completion of browser registration. */
477
- export declare interface WebMcpRegistrationDisposer {
478
- (): void;
479
- /** Rejects if the browser rejects any tool; all sibling tools are aborted. */
480
- readonly ready: Promise<void>;
481
- }
482
-
483
- /**
484
- * Canonical data-only definition for one API-backed browser tool. Unlike a
485
- * collection definition, this does not imply that a list route or client cache
486
- * exists. Generated definitions carry explicit semantics; manual legacy
487
- * literals may omit them and register with fail-closed defaults.
488
- */
489
- declare interface WebMcpToolDefinition extends WebToolDescriptor {
490
- /** Stable definition identity is `(collection, action)`. */
491
- collection: string;
492
- /** Canonical qualified row-model identity. */
493
- objectRef: string;
494
- /** Row-model class used by the shared MCP tool vocabulary. */
495
- className: string;
496
- endpoint: string;
497
- idField: string;
498
- idType: 'uuid' | 'text';
499
- /** Complete generated route metadata, including CRUD actions. */
500
- route: SmrtWebToolRouteDescriptor;
501
- /** Cache-invalidation edges for materialized sibling collections. */
502
- relationships: SmrtWebRelationship[];
503
- }
504
-
505
- /**
506
- * The one capability classification rule this package applies, extracted so
507
- * every declaration site in smrt-web shares a single implementation (#2587,
508
- * #2588).
509
- *
510
- * Two sites consume it today: `webmcp.ts` (canonical definitions trusted
511
- * through it directly, and its legacy CRUD switch's fail-closed default
512
- * branch) and `intents.ts` (a declared view intent, which is never a CRUD
513
- * verb and so resolves through the declaration rule alone). Keeping the rule
514
- * here rather than private to `webmcp.ts` is what lets the intent path be a
515
- * dependency-free module that never pulls the client-data engine.
516
- *
517
- * This module structurally mirrors `CapabilityEffect` / `CapabilityDeclaration`
518
- * / `CapabilityClassification` in `@happyvertical/smrt-types` rather than
519
- * importing them — this package's dependency-DAG guardrails keep it free of
520
- * every `@happyvertical/*` dependency (see AGENTS.md "No inter-smrt
521
- * dependencies"), the same reason `data-query.ts` mirrors that package's
522
- * bounded query envelope structurally instead of importing it.
523
- */
524
- /**
525
- * Browser/agent-visible effect classification for a capability. `'read'`
526
- * never mutates; `'write'` mutates within the application; `'destructive'`
527
- * may remove or irreversibly change data.
528
- */
529
- export declare type WebMcpToolEffect = 'read' | 'write' | 'destructive';
530
-
531
- /** Host-supplied per-invocation options, distinct from registration lifetime. */
532
- export declare interface WebMcpToolExecutionOptions {
533
- /** Caller cancellation. Older hosts may omit execution options entirely. */
534
- signal?: AbortSignal;
535
- }
536
-
537
- /**
538
- * Thrown synchronously when a name is already held. Carries the colliding
539
- * name and the owner that holds it so a host can report both without parsing
540
- * the message.
541
- */
542
- export declare class WebMcpToolNameCollisionError extends Error {
543
- readonly toolName: string;
544
- /** The path that already holds {@link toolName}. */
545
- readonly owner: WebMcpToolNameOwner;
546
- /** The path that tried to take it. */
547
- readonly requestedBy: WebMcpToolNameOwner;
548
- constructor(toolName: string,
549
- /** The path that already holds {@link toolName}. */
550
- owner: WebMcpToolNameOwner,
551
- /** The path that tried to take it. */
552
- requestedBy: WebMcpToolNameOwner);
553
- }
554
-
555
- /**
556
- * Document-global WebMCP tool-name lock (#2613).
557
- *
558
- * A browser tool name is derived independently on three paths that all end at
559
- * the same `document.modelContext`:
560
- *
561
- * 1. **generated** model tools — `<namespace>_<model>_<action>`, built by
562
- * `registerWebMcpTools` from the exposure policy;
563
- * 2. **ui** — the six fixed `smrt_ui_*` tools a UI layer registers under a
564
- * configurable prefix (`registerWebMcpUiTools` in `@happyvertical/smrt-svelte`);
565
- * 3. **intent** / **bespoke** — declared view intents (#2588) and hand-written
566
- * `useWebMcpTool` tools, whose names come from the declaration `id` or the
567
- * spec `name`.
568
- *
569
- * None of the three can see the others at declaration time: a namespace and a
570
- * UI prefix are runtime values a declaration never sees. `registerWebMcpTools`
571
- * rejects duplicates WITHIN its own prospective set, but nothing checked
572
- * ACROSS the paths, so a collision reached the host — which rejects the later
573
- * registration and leaves the tool silently absent.
574
- *
575
- * This module is the shared reservation table those paths coordinate through.
576
- * It is an AVAILABILITY guard, not a trust boundary: every path stays
577
- * fail-closed and browser-only regardless, and a collision loses a tool rather
578
- * than granting one. The authenticated REST surface remains the auth, tenant,
579
- * and field-write boundary.
580
- *
581
- * ## Why the table lives on the document, not in this module
582
- *
583
- * The lock must be shared by every registrar reachable from one page, which
584
- * spans package boundaries (`smrt-web` and a UI layer) and therefore possibly
585
- * more than one copy of this module — a second bundle chunk, a duplicated
586
- * dependency, or an HMR reload that replaces module state while the host
587
- * registry keeps every tool it already accepted. Module-level state loses the
588
- * reservations in all of those cases, and a lost reservation is worse than no
589
- * lock: it lets a duplicate through, or (on the release side) strands a name
590
- * forever. The table is therefore stored on the `document` object itself under
591
- * a cross-realm `Symbol.for` key, which every copy of this module resolves to
592
- * the same slot.
593
- *
594
- * The table is additionally stamped with the `modelContext` it was built for.
595
- * When a host installs a NEW model context on the same document — a fresh
596
- * origin-trial registry, or a test harness swapping the double — every tool
597
- * the old registry held is gone, so the table resets rather than stranding
598
- * those names.
599
- *
600
- * This module is dependency-free by design (see the package's dependency-DAG
601
- * guardrails) and ships as the `@happyvertical/smrt-web/webmcp-tool-names`
602
- * entry so a UI layer can reserve its fixed tool names without pulling in the
603
- * client-data engine.
604
- */
605
- /** Which registration path holds a reserved WebMCP tool name. */
606
- export declare type WebMcpToolNameOwner = 'generated' | 'ui' | 'intent' | 'bespoke';
607
-
608
- /**
609
- * The owner currently holding `name` on this document, or undefined. For
610
- * diagnostics and tests; registration paths use
611
- * {@link reserveWebMcpToolNames}, which is atomic.
612
- */
613
- export declare function webMcpToolNameOwner(name: string, options?: ReserveWebMcpToolNamesOptions): WebMcpToolNameOwner | undefined;
614
-
615
- /** Releases every name one reservation holds. Idempotent. */
616
- export declare interface WebMcpToolNameReservation {
617
- release(): void;
618
- }
619
-
620
- /**
621
- * One generated collection definition: everything needed to construct a client
622
- * collection over the generated REST surface. The `_row` property is a phantom
623
- * type carrier threaded through codegen — it never exists at runtime, it only
624
- * lets factories infer the row type from a definition.
625
- */
626
- /**
627
- * One WebMCP / MCP tool descriptor for a collection action (#1812). Emitted by
628
- * the core web-collections codegen as PLAIN DATA (this package has no smrt
629
- * dependency), shaped to match Chrome's `document.modelContext.registerTool`
630
- * input — see https://developer.chrome.com/docs/ai/webmcp. Consumed by
631
- * {@link registerWebMcpTools} in `./webmcp`.
632
- */
633
- declare interface WebToolDescriptor {
634
- /** The action this tool performs (`list` | `get` | … | a custom method name). */
635
- action: string;
636
- /** Tool id, `${className.toLowerCase()}_${action}` (e.g. `product_list`). */
637
- name: string;
638
- description: string;
639
- /** JSON Schema for the tool's arguments. */
640
- inputSchema: Record<string, unknown>;
641
- /** True for non-mutating reads → WebMCP `annotations.readOnlyHint`. */
642
- readOnly: boolean;
643
- /** Capability effect used by WebMCP exposure policy. */
644
- effect?: 'read' | 'write' | 'destructive';
645
- /** Whether repeating the tool with the same arguments is safe. */
646
- idempotent?: boolean;
647
- /** Whether the tool may interact outside the SMRT application. */
648
- openWorld?: boolean;
649
- /** Generated custom-route transport metadata. */
650
- route?: SmrtWebToolRouteDescriptor;
651
- }
652
-
653
- export { }
1
+ /**
2
+ * WebMCP browser tool registration (#1812).
3
+ *
4
+ * Registers each collection's generated {@link WebToolDescriptor}s with the
5
+ * browser's WebMCP registry (`document.modelContext.registerTool`) so an
6
+ * in-page AI agent can discover and invoke them — see
7
+ * https://developer.chrome.com/docs/ai/webmcp. Each tool's `execute` runs
8
+ * through the shared smrt-web collection AS THE PAGE'S AUTHENTICATED USER, so
9
+ * the generated REST guards (auth, tenant gate #1554, writable + sensitive-field
10
+ * policy #1540) are the security boundary — nothing is re-implemented here.
11
+ *
12
+ * Framework-agnostic: this module imports no UI framework. `document.modelContext`
13
+ * is a browser global, not a dependency. Legacy list-backed mutations use the
14
+ * same optimistic path as page code through `createSmrtCollection`; canonical
15
+ * tool-only definitions execute through REST fetchers and invalidate any
16
+ * materialized sibling caches through the host's shared `SmrtWebClient`.
17
+ */
18
+ import { type WebMcpToolEffect } from './capability-classification.js';
19
+ import { type SmrtCrudFetchers, type SmrtWebClient, type SmrtWebCollectionDefinition, type WebMcpToolDefinition } from './index.js';
20
+ import { type ViewIntent, type ViewIntentBinding } from './intents.js';
21
+ import { type WebMcpToolNameOwner } from './webmcp-tool-names.js';
22
+ /** Host-supplied per-invocation options, distinct from registration lifetime. */
23
+ export interface WebMcpToolExecutionOptions {
24
+ /** Caller cancellation. Older hosts may omit execution options entirely. */
25
+ signal?: AbortSignal;
26
+ }
27
+ /** Disposes a registration and exposes completion of browser registration. */
28
+ export interface WebMcpRegistrationDisposer {
29
+ (): void;
30
+ /** Rejects if the browser rejects any tool; all sibling tools are aborted. */
31
+ readonly ready: Promise<void>;
32
+ }
33
+ /**
34
+ * Re-exported from `./capability-classification.js`, where the one
35
+ * classification rule this package applies now lives so the declarative
36
+ * view-intent entry (#2588) can share it without importing this module.
37
+ */
38
+ export type { WebMcpToolEffect } from './capability-classification.js';
39
+ /**
40
+ * Re-exported from `./webmcp-tool-names.js` so a host catching a collision
41
+ * does not have to import the lock entry separately. That entry
42
+ * (`@happyvertical/smrt-web/webmcp-tool-names`) stays the import a UI layer
43
+ * uses to reserve its own fixed tool names, because it is dependency-free.
44
+ */
45
+ export { reserveWebMcpToolNames, WebMcpToolNameCollisionError, type WebMcpToolNameOwner, type WebMcpToolNameReservation, webMcpToolNameOwner, } from './webmcp-tool-names.js';
46
+ export interface WebMcpExposurePolicy {
47
+ /** Allowed effects. Omitted means read-only exposure. */
48
+ effects?: readonly WebMcpToolEffect[];
49
+ /** Prefix every registered tool name with `<namespace>_`. */
50
+ namespace?: string;
51
+ /** Optional maximum tools registered by one call. */
52
+ maxTools?: number;
53
+ }
54
+ /**
55
+ * A hand-written browser tool from application code — not generated from a
56
+ * `@smrt()` model. Structurally identical to the WebMCP `registerTool` input;
57
+ * kept as a separate type so this framework-agnostic module never depends on
58
+ * a UI layer's tool-spec type.
59
+ */
60
+ export interface WebMcpBespokeToolSpec {
61
+ name: string;
62
+ description: string;
63
+ inputSchema: Record<string, unknown>;
64
+ annotations?: {
65
+ readOnlyHint?: boolean;
66
+ destructiveHint?: boolean;
67
+ idempotentHint?: boolean;
68
+ openWorldHint?: boolean;
69
+ untrustedContentHint?: boolean;
70
+ };
71
+ execute: (args: Record<string, unknown>, options?: WebMcpToolExecutionOptions) => string | Promise<string>;
72
+ }
73
+ export interface RegisterWebMcpBespokeToolOptions {
74
+ /**
75
+ * Allowed effects. Omitted means read-only exposure — the same default as
76
+ * {@link registerWebMcpTools}. `namespace` and `maxTools` deliberately do
77
+ * not apply to a bespoke tool (#2586): a component author already chose a
78
+ * stable name, and counting one intent against a shared budget could make
79
+ * an unrelated generated tool set fail to register.
80
+ */
81
+ effects?: readonly WebMcpToolEffect[];
82
+ /**
83
+ * Which path this registration belongs to, used ONLY to label its
84
+ * tool-name reservation (#2613) so a later collision names the right
85
+ * source. Defaults to `bespoke`.
86
+ *
87
+ * A UI-framework binding that compiles a declared view intent itself and
88
+ * registers it here — rather than through {@link registerViewIntent} —
89
+ * must pass `'intent'`. `useViewIntent` in `@happyvertical/smrt-svelte`
90
+ * does exactly that, because reusing `useWebMcpTool`'s single WebMCP
91
+ * lifecycle is a documented invariant of that package; without this it
92
+ * would be the only shipped intent path and every intent collision would
93
+ * blame a `useWebMcpTool` call that does not exist.
94
+ *
95
+ * Purely a diagnostic label: it grants no capability, narrows no policy,
96
+ * and changes nothing about how the tool registers or executes.
97
+ */
98
+ owner?: Extract<WebMcpToolNameOwner, 'intent' | 'bespoke'>;
99
+ }
100
+ export interface RegisterWebMcpToolsOptions extends WebMcpExposurePolicy {
101
+ /** REST base path for the fetchers (default `/api/v1`). */
102
+ basePath?: string;
103
+ /** Injectable fetch (tests / SSR-safe wrappers). */
104
+ fetchFn?: typeof fetch;
105
+ /** Shared smrt-web cache handle used by page collections. */
106
+ client?: SmrtWebClient;
107
+ /** Optional cache scope matching the page collection's scope. */
108
+ scope?: string;
109
+ /**
110
+ * Override how a definition's CRUD fetchers are built. Defaults to
111
+ * {@link createDefinitionFetchers}; the primary seam for testing `execute`
112
+ * without a live server.
113
+ */
114
+ resolveFetchers?: (definition: SmrtWebCollectionDefinition) => SmrtCrudFetchers;
115
+ /**
116
+ * Override direct REST fetchers for a canonical tool-only definition.
117
+ * Fetchers are optional because a get-only or custom-action-only model does
118
+ * not have a list or create route.
119
+ */
120
+ resolveToolFetchers?: (definition: WebMcpToolDefinition) => Partial<SmrtCrudFetchers>;
121
+ /** Predicate to include/exclude individual tools (e.g. reads-only surfaces). */
122
+ filter?: (definition: SmrtWebCollectionDefinition, descriptor: NonNullable<SmrtWebCollectionDefinition['toolDescriptors']>[number]) => boolean;
123
+ /** Predicate for canonical per-tool definitions. */
124
+ filterTool?: (definition: WebMcpToolDefinition) => boolean;
125
+ }
126
+ /** Accepted legacy collection definitions and canonical per-tool definitions. */
127
+ export type WebMcpRegistrationDefinition = SmrtWebCollectionDefinition | WebMcpToolDefinition;
128
+ /**
129
+ * Register every collection's generated tool descriptors with WebMCP.
130
+ *
131
+ * Every selected tool name is claimed against the document-global tool-name
132
+ * lock (#2613) before the first browser registration, so a collision with a
133
+ * UI-layer or bespoke tool throws {@link WebMcpToolNameCollisionError} with
134
+ * the offending name and its current owner instead of reaching the host.
135
+ *
136
+ * @returns a disposer that deregisters all tools this call registered. On a
137
+ * browser without WebMCP the call is a no-op and the disposer is inert.
138
+ * @throws {WebMcpToolNameCollisionError} when a selected name is already held.
139
+ */
140
+ export declare function registerWebMcpTools(definitions: readonly WebMcpRegistrationDefinition[], options?: RegisterWebMcpToolsOptions): WebMcpRegistrationDisposer;
141
+ /**
142
+ * Register one hand-written browser tool through the same fail-closed effect
143
+ * classification and `effects` exposure policy as {@link registerWebMcpTools}
144
+ * (#2586). A tool with no `annotations`, or with annotations that leave its
145
+ * effect undeclared, classifies destructive/non-idempotent/open-world — the
146
+ * same default `actionSemantics` gives an undeclared custom model action —
147
+ * and is excluded unless the policy allows `destructive`. `namespace` and
148
+ * `maxTools` are out of scope for a bespoke tool; see
149
+ * {@link RegisterWebMcpBespokeToolOptions}.
150
+ *
151
+ * The tool's name is also claimed against the
152
+ * document-global tool-name lock before the browser sees it. A name a
153
+ * generated model tool, a fixed UI tool, or another live bespoke tool /
154
+ * intent already holds throws {@link WebMcpToolNameCollisionError}
155
+ * SYNCHRONOUSLY, naming the colliding name and the owner that holds it.
156
+ * Previously the host accepted the call and silently dropped the losing
157
+ * registration. Disposing releases the name, so register/dispose/re-register
158
+ * under one name still succeeds.
159
+ *
160
+ * @returns a disposer that deregisters the tool this call registered (a
161
+ * no-op double-call). On a browser without WebMCP, or when policy excludes
162
+ * the tool's effect, the call is a no-op and the disposer is inert.
163
+ * @throws {WebMcpToolNameCollisionError} when the name is already held.
164
+ */
165
+ export declare function registerWebMcpBespokeTool(spec: WebMcpBespokeToolSpec, options?: RegisterWebMcpBespokeToolOptions): WebMcpRegistrationDisposer;
166
+ /**
167
+ * Register one declared view intent (#2588) against a mounted registry
168
+ * binding, for as long as the returned disposer is not called.
169
+ *
170
+ * This is the ONLY way an intent reaches the browser, and it goes through
171
+ * {@link registerWebMcpBespokeTool}, so an intent inherits the same
172
+ * fail-closed `effects` exposure policy as a generated model tool. The
173
+ * intent's classification was resolved at declaration time by the shared
174
+ * rule, and the hints `compileViewIntentToolSpec` emits round-trip through
175
+ * the bespoke registrar's own re-resolution unchanged.
176
+ *
177
+ * The `execute` registered here is constructed by
178
+ * `compileViewIntentToolSpec` from `intent.target`. No author-supplied code
179
+ * runs, and the only thing it can do is dispatch one browser registry
180
+ * command — the runtime half of the no-REST invariant.
181
+ *
182
+ * The intent's derived tool name is claimed against the document-global
183
+ * tool-name lock (#2613), so an id whose flattened name collides with a
184
+ * generated model tool or a fixed UI tool throws
185
+ * {@link WebMcpToolNameCollisionError} here rather than silently losing the
186
+ * tool at the host. `defineIntent`'s own intent-vs-intent check cannot see
187
+ * either, because both depend on a runtime `namespace` / `prefix` the
188
+ * declaration never sees.
189
+ *
190
+ * @throws {WebMcpToolNameCollisionError} when the derived name is already held.
191
+ */
192
+ export declare function registerViewIntent(intent: ViewIntent, binding: ViewIntentBinding, options?: RegisterWebMcpBespokeToolOptions): WebMcpRegistrationDisposer;
193
+ //# sourceMappingURL=webmcp.d.ts.map