@happyvertical/smrt-svelte 0.43.10 → 0.44.0

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 (90) hide show
  1. package/AGENTS.md +38 -2
  2. package/dist/Provider.svelte +11 -0
  3. package/dist/Provider.svelte.d.ts.map +1 -1
  4. package/dist/components/forms/Form.svelte +29 -13
  5. package/dist/components/forms/Form.svelte.d.ts +5 -0
  6. package/dist/components/forms/Form.svelte.d.ts.map +1 -1
  7. package/dist/components/forms/__tests__/Form.webmcp.test.js +115 -6
  8. package/dist/components/settings/SettingsCatalog.svelte +9 -0
  9. package/dist/components/settings/SettingsCatalog.svelte.d.ts +9 -0
  10. package/dist/components/settings/SettingsCatalog.svelte.d.ts.map +1 -1
  11. package/dist/components/workspace/admin-shell/ActivityBadge.svelte +2 -0
  12. package/dist/components/workspace/admin-shell/ActivityBadge.svelte.d.ts +2 -0
  13. package/dist/components/workspace/admin-shell/ActivityBadge.svelte.d.ts.map +1 -1
  14. package/dist/components/workspace/admin-shell/ActivityItem.svelte +1 -0
  15. package/dist/components/workspace/admin-shell/ActivityItem.svelte.d.ts +1 -0
  16. package/dist/components/workspace/admin-shell/ActivityItem.svelte.d.ts.map +1 -1
  17. package/dist/components/workspace/admin-shell/ActivityList.svelte +3 -0
  18. package/dist/components/workspace/admin-shell/ActivityList.svelte.d.ts +3 -0
  19. package/dist/components/workspace/admin-shell/ActivityList.svelte.d.ts.map +1 -1
  20. package/dist/components/workspace/admin-shell/ActivityToasts.svelte +1 -0
  21. package/dist/components/workspace/admin-shell/ActivityToasts.svelte.d.ts +1 -0
  22. package/dist/components/workspace/admin-shell/ActivityToasts.svelte.d.ts.map +1 -1
  23. package/dist/components/workspace/admin-shell/AdminShell.svelte +15 -0
  24. package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts +15 -0
  25. package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -1
  26. package/dist/components/workspace/admin-shell/AppScopePanel.svelte +7 -0
  27. package/dist/components/workspace/admin-shell/AppScopePanel.svelte.d.ts +7 -0
  28. package/dist/components/workspace/admin-shell/AppScopePanel.svelte.d.ts.map +1 -1
  29. package/dist/components/workspace/admin-shell/HotkeyInput.svelte +4 -0
  30. package/dist/components/workspace/admin-shell/HotkeyInput.svelte.d.ts +4 -0
  31. package/dist/components/workspace/admin-shell/HotkeyInput.svelte.d.ts.map +1 -1
  32. package/dist/components/workspace/admin-shell/ShellCorner.svelte +1 -0
  33. package/dist/components/workspace/admin-shell/ShellCorner.svelte.d.ts +1 -0
  34. package/dist/components/workspace/admin-shell/ShellCorner.svelte.d.ts.map +1 -1
  35. package/dist/components/workspace/admin-shell/ShortcutsOverlay.svelte +1 -0
  36. package/dist/components/workspace/admin-shell/ShortcutsOverlay.svelte.d.ts +1 -0
  37. package/dist/components/workspace/admin-shell/ShortcutsOverlay.svelte.d.ts.map +1 -1
  38. package/dist/components/workspace/admin-shell/SystemScopePanel.svelte +2 -0
  39. package/dist/components/workspace/admin-shell/SystemScopePanel.svelte.d.ts +2 -0
  40. package/dist/components/workspace/admin-shell/SystemScopePanel.svelte.d.ts.map +1 -1
  41. package/dist/components/workspace/admin-shell/SystemStatusChips.svelte +1 -0
  42. package/dist/components/workspace/admin-shell/SystemStatusChips.svelte.d.ts +1 -0
  43. package/dist/components/workspace/admin-shell/SystemStatusChips.svelte.d.ts.map +1 -1
  44. package/dist/components/workspace/admin-shell/TenantNav.svelte +5 -0
  45. package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts +5 -0
  46. package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -1
  47. package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte +11 -0
  48. package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte.d.ts +11 -0
  49. package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte.d.ts.map +1 -1
  50. package/dist/index.d.ts +1 -0
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +3 -0
  53. package/dist/web/__tests__/view-intent-provider.fixture.svelte +28 -0
  54. package/dist/web/__tests__/view-intent-provider.fixture.svelte.d.ts +13 -0
  55. package/dist/web/__tests__/view-intent-provider.fixture.svelte.d.ts.map +1 -0
  56. package/dist/web/__tests__/view-intent.fixture.svelte +33 -0
  57. package/dist/web/__tests__/view-intent.fixture.svelte.d.ts +15 -0
  58. package/dist/web/__tests__/view-intent.fixture.svelte.d.ts.map +1 -0
  59. package/dist/web/__tests__/view-intent.intents.d.ts +14 -0
  60. package/dist/web/__tests__/view-intent.intents.d.ts.map +1 -0
  61. package/dist/web/__tests__/view-intent.intents.js +38 -0
  62. package/dist/web/__tests__/view-intent.svelte.test.js +201 -0
  63. package/dist/web/__tests__/webmcp-composed.child.fixture.svelte +11 -0
  64. package/dist/web/__tests__/webmcp-composed.child.fixture.svelte.d.ts.map +1 -1
  65. package/dist/web/__tests__/webmcp-composed.integration.svelte.test.js +4 -0
  66. package/dist/web/__tests__/webmcp-effects-reactivity.child.fixture.svelte +16 -0
  67. package/dist/web/__tests__/webmcp-effects-reactivity.child.fixture.svelte.d.ts +19 -0
  68. package/dist/web/__tests__/webmcp-effects-reactivity.child.fixture.svelte.d.ts.map +1 -0
  69. package/dist/web/__tests__/webmcp-effects-reactivity.fixture.svelte +11 -0
  70. package/dist/web/__tests__/webmcp-effects-reactivity.fixture.svelte.d.ts +8 -0
  71. package/dist/web/__tests__/webmcp-effects-reactivity.fixture.svelte.d.ts.map +1 -0
  72. package/dist/web/__tests__/webmcp-harness-reactive-samename.svelte +20 -0
  73. package/dist/web/__tests__/webmcp-harness-reactive-samename.svelte.d.ts +7 -0
  74. package/dist/web/__tests__/webmcp-harness-reactive-samename.svelte.d.ts.map +1 -0
  75. package/dist/web/__tests__/webmcp-harness-unannotated.svelte +14 -0
  76. package/dist/web/__tests__/webmcp-harness-unannotated.svelte.d.ts +19 -0
  77. package/dist/web/__tests__/webmcp-harness-unannotated.svelte.d.ts.map +1 -0
  78. package/dist/web/__tests__/webmcp-harness.svelte +4 -0
  79. package/dist/web/__tests__/webmcp-harness.svelte.d.ts.map +1 -1
  80. package/dist/web/__tests__/webmcp.test.js +136 -9
  81. package/dist/web/view-intent.svelte.d.ts +64 -0
  82. package/dist/web/view-intent.svelte.d.ts.map +1 -0
  83. package/dist/web/view-intent.svelte.js +83 -0
  84. package/dist/web/webmcp-bespoke-context.d.ts +14 -0
  85. package/dist/web/webmcp-bespoke-context.d.ts.map +1 -0
  86. package/dist/web/webmcp-bespoke-context.js +8 -0
  87. package/dist/web/webmcp.svelte.d.ts +29 -13
  88. package/dist/web/webmcp.svelte.d.ts.map +1 -1
  89. package/dist/web/webmcp.svelte.js +92 -12
  90. package/package.json +8 -7
@@ -0,0 +1,83 @@
1
+ // The dependency-free intent entry, NOT the package root: it pulls no
2
+ // client-data engine, so a component binding an intent pays only for the
3
+ // declaration contract. The registrar itself is still loaded lazily, by
4
+ // `useWebMcpTool`.
5
+ import { compileViewIntentToolSpec } from '@happyvertical/smrt-web/intents';
6
+ import { useWebMcpTool } from './webmcp.svelte.js';
7
+ import { tryGetWebMcpUiContext } from './webmcp-ui-context.js';
8
+ /**
9
+ * Bind a declared view intent (#2588) to this component's mounted registry
10
+ * identity, for exactly this component's lifetime.
11
+ *
12
+ * Svelte is the FIRST binding, not the only one: the declaration contract,
13
+ * the registry, and the compilation to a browser tool all live in
14
+ * `@happyvertical/smrt-web` with no Svelte dependency. This file is the thin
15
+ * part — it resolves the mounted registries from the nearest Provider and
16
+ * hands the compiled spec to {@link useWebMcpTool}, which already owns the
17
+ * WebMCP lifecycle: the synchronous Provider policy read, the `options.effects`
18
+ * fallback, serialized same-name re-registration across effect reruns, and
19
+ * the lazy `@happyvertical/smrt-web` import.
20
+ *
21
+ * Registration therefore goes through `registerWebMcpBespokeTool`, so an
22
+ * intent is subject to the same fail-closed exposure policy as a generated
23
+ * model tool. Execution dispatches exactly one registry command with
24
+ * `source: 'agent'` — `StagedControlReview` stays on the path, agent-staged
25
+ * values remain proposals, and there is no path to REST.
26
+ *
27
+ * Declare the intent itself at module scope in a `.ts` sidecar
28
+ * (`Foo.intents.ts`) so #2591's scanner can emit it; import it here.
29
+ *
30
+ * With no Provider ancestor and no explicit registry override, this is a
31
+ * silent no-op — an intent with nothing mounted to dispatch to registers
32
+ * nothing, the same way every WebMCP entry point no-ops off-WebMCP. Because
33
+ * the registries are resolved INSIDE the tool factory, a Provider that
34
+ * enables `webmcp.ui` after this component initializes, or that swaps a
35
+ * registry, re-runs the effect and registers the intent against the current
36
+ * one rather than staying stuck on what was there at init.
37
+ *
38
+ * @throws if `identity` does not match the intent's declared target registry,
39
+ * or contradicts an identity the declaration pinned — both are author errors,
40
+ * not environment differences.
41
+ */
42
+ export function useViewIntent(intent, options) {
43
+ // `getContext` is only valid during component initialization, so the
44
+ // CONTEXT OBJECT must be captured here. Its `enabled` and registry members
45
+ // are reactive getters, though, and reading them here would freeze one
46
+ // value for the component's whole life — so they are read inside the
47
+ // factory below, which `useWebMcpTool` runs within its `$effect`.
48
+ const uiContext = tryGetWebMcpUiContext();
49
+ // The identity SHAPE is a static author decision, so it is checked eagerly:
50
+ // a mismatch surfaces at the call site rather than inside an effect, where
51
+ // a throw is harder to attribute. Only the registries are deferred.
52
+ assertIdentityShape(intent, options.identity);
53
+ useWebMcpTool(() => {
54
+ const controlRegistry = options.controlRegistry ??
55
+ (uiContext?.enabled ? uiContext.controlRegistry : undefined);
56
+ const dataSurfaceRegistry = options.dataSurfaceRegistry ??
57
+ (uiContext?.enabled ? uiContext.dataSurfaceRegistry : undefined);
58
+ const binding = resolveBinding(intent, options.identity, controlRegistry, dataSurfaceRegistry);
59
+ return binding ? compileViewIntentToolSpec(intent, binding) : null;
60
+ }, options.effects ? { effects: options.effects } : {});
61
+ }
62
+ function assertIdentityShape(intent, identity) {
63
+ if (intent.target.registry === 'control' && !('formId' in identity)) {
64
+ throw new Error(`[smrt-svelte] intent '${intent.id}' targets the control registry and needs a { formId, controlId } identity`);
65
+ }
66
+ if (intent.target.registry === 'dataSurface' && !('surfaceId' in identity)) {
67
+ throw new Error(`[smrt-svelte] intent '${intent.id}' targets the dataSurface registry and needs a { surfaceId, kind } identity`);
68
+ }
69
+ }
70
+ function resolveBinding(intent, identity, controlRegistry, dataSurfaceRegistry) {
71
+ if (intent.target.registry === 'control') {
72
+ if (!controlRegistry || !('formId' in identity))
73
+ return null;
74
+ return { registry: 'control', registryPort: controlRegistry, identity };
75
+ }
76
+ if (!dataSurfaceRegistry || !('surfaceId' in identity))
77
+ return null;
78
+ return {
79
+ registry: 'dataSurface',
80
+ registryPort: dataSurfaceRegistry,
81
+ identity,
82
+ };
83
+ }
@@ -0,0 +1,14 @@
1
+ import type { WebMcpToolEffect } from '@happyvertical/smrt-web';
2
+ /**
3
+ * The `effects` exposure policy `useWebMcpTool` applies to a bespoke
4
+ * component tool (#2586). Provider sets this from the same `webmcp.effects`
5
+ * config it passes to `registerWebMcpTools` for generated tools, so a
6
+ * bespoke and a generated tool share one policy. Absent a Provider ancestor,
7
+ * `useWebMcpTool` falls back to the registrar's own default (read-only).
8
+ */
9
+ export interface WebMcpBespokeContext {
10
+ readonly effects?: readonly WebMcpToolEffect[];
11
+ }
12
+ export declare function setWebMcpBespokeContext(context: WebMcpBespokeContext): void;
13
+ export declare function tryGetWebMcpBespokeContext(): WebMcpBespokeContext | null;
14
+ //# sourceMappingURL=webmcp-bespoke-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webmcp-bespoke-context.d.ts","sourceRoot":"","sources":["../../src/web/webmcp-bespoke-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAKhE;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAChD;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAE3E;AAED,wBAAgB,0BAA0B,IAAI,oBAAoB,GAAG,IAAI,CAExE"}
@@ -0,0 +1,8 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ const WEBMCP_BESPOKE_CONTEXT_KEY = Symbol('smrt-webmcp-bespoke-context');
3
+ export function setWebMcpBespokeContext(context) {
4
+ setContext(WEBMCP_BESPOKE_CONTEXT_KEY, context);
5
+ }
6
+ export function tryGetWebMcpBespokeContext() {
7
+ return getContext(WEBMCP_BESPOKE_CONTEXT_KEY) ?? null;
8
+ }
@@ -1,16 +1,16 @@
1
+ import type { WebMcpBespokeToolSpec, WebMcpToolEffect } from '@happyvertical/smrt-web';
1
2
  /** A tool exposed to the browser's WebMCP model context. */
2
- export interface WebMcpToolSpec {
3
- name: string;
4
- description: string;
5
- inputSchema: Record<string, unknown>;
6
- annotations?: {
7
- readOnlyHint?: boolean;
8
- destructiveHint?: boolean;
9
- idempotentHint?: boolean;
10
- openWorldHint?: boolean;
11
- untrustedContentHint?: boolean;
12
- };
13
- execute: (args: Record<string, unknown>) => string | Promise<string>;
3
+ export type WebMcpToolSpec = WebMcpBespokeToolSpec;
4
+ export interface UseWebMcpToolOptions {
5
+ /**
6
+ * This tool's own `effects` exposure-policy fallback, used only when no
7
+ * Provider ancestor supplies an explicit `webmcp.effects` policy. A
8
+ * Provider's explicit policy always wins over this default — even a
9
+ * narrower one — so a component cannot use its own default to grant
10
+ * itself more than an ancestor Provider allows (#2586). Omit to keep the
11
+ * registrar's own read-only default as the no-Provider fallback.
12
+ */
13
+ effects?: readonly WebMcpToolEffect[];
14
14
  }
15
15
  declare global {
16
16
  interface Document {
@@ -26,6 +26,22 @@ declare global {
26
26
  * Register a component-owned WebMCP intent for exactly that component's
27
27
  * lifetime. The factory runs inside the effect so rune dependencies used by a
28
28
  * bespoke intent cause the tool to be replaced when its spec changes.
29
+ *
30
+ * Routes through `@happyvertical/smrt-web`'s `registerWebMcpBespokeTool` so a
31
+ * bespoke tool is subject to the same fail-closed `effects` exposure policy
32
+ * as generated model tools (#2586): a spec with no `annotations`, or with
33
+ * annotations that leave its effect undeclared, classifies destructive,
34
+ * non-idempotent, open-world, and is excluded unless policy allows
35
+ * `destructive`. The policy is the nearest Provider's `webmcp.effects` (read
36
+ * from context so a bespoke and a generated tool share one policy); absent a
37
+ * Provider ancestor, it falls back to the registrar's own read-only default.
38
+ * `namespace` and `maxTools` deliberately do not apply to a bespoke tool.
39
+ *
40
+ * `@happyvertical/smrt-web` loads lazily, on first use, so a page that only
41
+ * calls `useWebMcpTool` never bundles the client-data engine.
42
+ *
43
+ * @param options.effects a fallback exposure policy applied only when no
44
+ * Provider ancestor declares one — see {@link UseWebMcpToolOptions}.
29
45
  */
30
- export declare function useWebMcpTool(factory: () => WebMcpToolSpec | null | undefined): void;
46
+ export declare function useWebMcpTool(factory: () => WebMcpToolSpec | null | undefined, options?: UseWebMcpToolOptions): void;
31
47
  //# sourceMappingURL=webmcp.svelte.d.ts.map
@@ -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,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"}
1
+ {"version":3,"file":"webmcp.svelte.d.ts","sourceRoot":"","sources":["../../src/web/webmcp.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EAErB,gBAAgB,EACjB,MAAM,yBAAyB,CAAC;AAGjC,4DAA4D;AAC5D,MAAM,MAAM,cAAc,GAAG,qBAAqB,CAAC;AAEnD,MAAM,WAAW,oBAAoB;IACnC;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;CACvC;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;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,cAAc,GAAG,IAAI,GAAG,SAAS,EAChD,OAAO,GAAE,oBAAyB,GACjC,IAAI,CAuFN"}
@@ -1,3 +1,4 @@
1
+ import { tryGetWebMcpBespokeContext } from './webmcp-bespoke-context.js';
1
2
  function getModelContext() {
2
3
  // Do not read `document` at module evaluation time: this module is imported
3
4
  // by SSR bundles and by browsers without the WebMCP origin trial.
@@ -11,22 +12,101 @@ function getModelContext() {
11
12
  * Register a component-owned WebMCP intent for exactly that component's
12
13
  * lifetime. The factory runs inside the effect so rune dependencies used by a
13
14
  * bespoke intent cause the tool to be replaced when its spec changes.
15
+ *
16
+ * Routes through `@happyvertical/smrt-web`'s `registerWebMcpBespokeTool` so a
17
+ * bespoke tool is subject to the same fail-closed `effects` exposure policy
18
+ * as generated model tools (#2586): a spec with no `annotations`, or with
19
+ * annotations that leave its effect undeclared, classifies destructive,
20
+ * non-idempotent, open-world, and is excluded unless policy allows
21
+ * `destructive`. The policy is the nearest Provider's `webmcp.effects` (read
22
+ * from context so a bespoke and a generated tool share one policy); absent a
23
+ * Provider ancestor, it falls back to the registrar's own read-only default.
24
+ * `namespace` and `maxTools` deliberately do not apply to a bespoke tool.
25
+ *
26
+ * `@happyvertical/smrt-web` loads lazily, on first use, so a page that only
27
+ * calls `useWebMcpTool` never bundles the client-data engine.
28
+ *
29
+ * @param options.effects a fallback exposure policy applied only when no
30
+ * Provider ancestor declares one — see {@link UseWebMcpToolOptions}.
14
31
  */
15
- export function useWebMcpTool(factory) {
32
+ export function useWebMcpTool(factory, options = {}) {
33
+ const bespokeContext = tryGetWebMcpBespokeContext();
34
+ // Survives across effect reruns (unlike the per-run `dispose`/`cancelled`
35
+ // locals below) so a new run can serialize its registration against the
36
+ // previous run's — see the comment at its read site.
37
+ let previousDisposer;
16
38
  $effect(() => {
17
- const context = getModelContext();
18
- if (!context)
39
+ if (typeof window === 'undefined')
19
40
  return;
20
- const spec = factory();
21
- if (!spec)
41
+ // Feature-detect locally first so a browser without WebMCP never pays for
42
+ // the dynamic import below.
43
+ if (!getModelContext())
22
44
  return;
23
- const controller = new AbortController();
24
- const registration = context.registerTool(spec, {
25
- signal: controller.signal,
26
- });
27
- if (registration) {
28
- void Promise.resolve(registration).catch(() => controller.abort());
45
+ const factorySpec = factory();
46
+ if (!factorySpec)
47
+ return;
48
+ // Rebind to a definitely-non-nullish const: TypeScript does not carry
49
+ // the `!factorySpec` narrowing above into the `register` closure below,
50
+ // since it runs later, asynchronously.
51
+ const spec = factorySpec;
52
+ // Read the Provider's effects policy synchronously, inside this effect's
53
+ // tracking window — `bespokeContext.effects` is a reactive getter, and a
54
+ // read from inside the dynamic import's `.then()` below happens after
55
+ // the effect has already finished its synchronous run, so it would never
56
+ // register as a dependency. Capturing it here means a later change to
57
+ // the Provider's policy re-runs this effect, tearing down and
58
+ // re-registering the tool under the new policy. A Provider's explicit
59
+ // policy always wins; this call's own `options.effects` is only the
60
+ // no-Provider fallback.
61
+ const effects = bespokeContext?.effects ?? options.effects;
62
+ // Snapshotted before this run's async work starts: the previous run's
63
+ // cleanup (below) synchronously aborts its controller, but a host is not
64
+ // required to finish processing that abort before this run's dynamic
65
+ // import resolves. Awaiting the prior disposer's `.ready` settling first
66
+ // serializes same-name re-registration so this run never asks the host
67
+ // to accept a new registration while it may still consider the old one
68
+ // live.
69
+ const priorReady = previousDisposer?.ready;
70
+ let cancelled = false;
71
+ let dispose = () => { };
72
+ async function register() {
73
+ // The dynamic import failing to load is expected in bundle profiles
74
+ // without `@happyvertical/smrt-web` — treat it like WebMCP being
75
+ // unavailable and no-op, silently. Only THIS statement's failure is
76
+ // caught: a synchronous throw from `registerWebMcpBespokeTool` below
77
+ // (e.g. an invalid `effects` policy) happens outside this try block
78
+ // and is deliberately left to propagate as an unhandled rejection of
79
+ // `register()`'s own promise, rather than being swallowed alongside
80
+ // the load-failure case.
81
+ let registerWebMcpBespokeTool;
82
+ try {
83
+ ({ registerWebMcpBespokeTool } = await import('@happyvertical/smrt-web'));
84
+ }
85
+ catch {
86
+ return;
87
+ }
88
+ if (cancelled)
89
+ return;
90
+ if (priorReady) {
91
+ await priorReady.catch(() => undefined);
92
+ // A newer run may have superseded this one while the wait above
93
+ // was pending; re-check before registering under this run's name.
94
+ if (cancelled)
95
+ return;
96
+ }
97
+ const registration = registerWebMcpBespokeTool(spec, { effects });
98
+ dispose = registration;
99
+ previousDisposer = registration;
100
+ void registration.ready.catch((error) => {
101
+ if (!cancelled) {
102
+ console.warn('[smrt-svelte] bespoke WebMCP tool registration failed', spec.name, error);
103
+ }
104
+ });
29
105
  }
30
- return () => controller.abort();
106
+ void register();
107
+ return () => {
108
+ cancelled = true;
109
+ dispose();
110
+ };
31
111
  });
32
112
  }
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-svelte",
3
- "version": "0.43.10",
3
+ "version": "0.44.0",
4
+ "smrtJsdoc": "strict",
4
5
  "description": "Svelte 5 components for SMRT user management - auth, users, tenants, roles, permissions, groups",
5
6
  "type": "module",
6
7
  "smrtRawPrimitives": "strict",
@@ -123,10 +124,10 @@
123
124
  "@tanstack/db": "^0.6.14",
124
125
  "@tanstack/svelte-db": "^0.1.91",
125
126
  "esm-env": "^1.2.2",
126
- "@happyvertical/smrt-languages": "0.43.10",
127
- "@happyvertical/smrt-types": "0.43.10",
128
- "@happyvertical/smrt-ui": "0.43.10",
129
- "@happyvertical/smrt-web": "0.43.10"
127
+ "@happyvertical/smrt-languages": "0.44.0",
128
+ "@happyvertical/smrt-types": "0.44.0",
129
+ "@happyvertical/smrt-ui": "0.44.0",
130
+ "@happyvertical/smrt-web": "0.44.0"
130
131
  },
131
132
  "peerDependencies": {
132
133
  "@huggingface/transformers": ">=3.8.1",
@@ -166,8 +167,8 @@
166
167
  "typescript": "5.9.3",
167
168
  "vite": "8.1.4",
168
169
  "vitest": "4.1.10",
169
- "@happyvertical/smrt-core": "0.43.10",
170
- "@happyvertical/smrt-scanner": "0.43.10"
170
+ "@happyvertical/smrt-core": "0.44.0",
171
+ "@happyvertical/smrt-scanner": "0.44.0"
171
172
  },
172
173
  "scripts": {
173
174
  "build": "svelte-package -i src --tsconfig tsconfig.svelte.json",