@happyvertical/smrt-svelte 0.53.0 → 0.53.2

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 (254) hide show
  1. package/package.json +15 -15
  2. package/dist/__tests__/app-state.test.js +0 -156
  3. package/dist/__tests__/data-surface-bridge.test.js +0 -351
  4. package/dist/__tests__/provider-mode-harness.svelte +0 -14
  5. package/dist/__tests__/provider-mode-harness.svelte.d.ts +0 -11
  6. package/dist/__tests__/provider-mode-harness.svelte.d.ts.map +0 -1
  7. package/dist/__tests__/provider-mode-probe.svelte +0 -13
  8. package/dist/__tests__/provider-mode-probe.svelte.d.ts +0 -8
  9. package/dist/__tests__/provider-mode-probe.svelte.d.ts.map +0 -1
  10. package/dist/__tests__/provider-webmcp-harness.svelte +0 -14
  11. package/dist/__tests__/provider-webmcp-harness.svelte.d.ts +0 -8
  12. package/dist/__tests__/provider-webmcp-harness.svelte.d.ts.map +0 -1
  13. package/dist/__tests__/success-never-text.test.js +0 -100
  14. package/dist/__tests__/warm-clients.test.js +0 -186
  15. package/dist/browser-ai/adapters/llm/bitgpu-inference-backend.test.js +0 -464
  16. package/dist/browser-ai/adapters/llm/webllm-inference-backend.test.js +0 -646
  17. package/dist/browser-ai/adapters/stt/browser-speech.test.js +0 -113
  18. package/dist/browser-ai/adapters/stt/dictation-source.test.js +0 -52
  19. package/dist/browser-ai/svelte/components/__tests__/ModelStatusControl.test.js +0 -172
  20. package/dist/components/board/__tests__/Board.test.js +0 -498
  21. package/dist/components/board/__tests__/BoardSsrHarness.svelte +0 -30
  22. package/dist/components/board/__tests__/BoardSsrHarness.svelte.d.ts +0 -19
  23. package/dist/components/board/__tests__/BoardSsrHarness.svelte.d.ts.map +0 -1
  24. package/dist/components/forms/__tests__/AddressInput.behavior.test.js +0 -216
  25. package/dist/components/forms/__tests__/CheckboxInput.test.js +0 -92
  26. package/dist/components/forms/__tests__/DateRangeInput.behavior.test.js +0 -135
  27. package/dist/components/forms/__tests__/DateTimeInput.behavior.test.js +0 -113
  28. package/dist/components/forms/__tests__/FileUpload.error-alert.test.js +0 -37
  29. package/dist/components/forms/__tests__/FileUpload.native-form.test.js +0 -249
  30. package/dist/components/forms/__tests__/FileUpload.test.js +0 -90
  31. package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte +0 -24
  32. package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte.d.ts +0 -10
  33. package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte.d.ts.map +0 -1
  34. package/dist/components/forms/__tests__/Form.behavior.test.js +0 -137
  35. package/dist/components/forms/__tests__/Form.extraction.test.js +0 -68
  36. package/dist/components/forms/__tests__/Form.stt-error.test.js +0 -57
  37. package/dist/components/forms/__tests__/Form.test.js +0 -86
  38. package/dist/components/forms/__tests__/Form.ui-controls.test.js +0 -119
  39. package/dist/components/forms/__tests__/Form.webmcp.test.js +0 -2336
  40. package/dist/components/forms/__tests__/FormMicButton.test.js +0 -92
  41. package/dist/components/forms/__tests__/FormScope.test.js +0 -39
  42. package/dist/components/forms/__tests__/MeasurementInput.behavior.test.js +0 -129
  43. package/dist/components/forms/__tests__/MoneyInput.behavior.test.js +0 -124
  44. package/dist/components/forms/__tests__/NumberInput.behavior.test.js +0 -151
  45. package/dist/components/forms/__tests__/PhoneInput.behavior.test.js +0 -96
  46. package/dist/components/forms/__tests__/SearchInput.test.js +0 -79
  47. package/dist/components/forms/__tests__/SelectInput.behavior.test.js +0 -132
  48. package/dist/components/forms/__tests__/TextInput.behavior.test.js +0 -131
  49. package/dist/components/forms/__tests__/TextareaInput.behavior.test.js +0 -96
  50. package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte +0 -30
  51. package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte.d.ts +0 -7
  52. package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte.d.ts.map +0 -1
  53. package/dist/components/forms/__tests__/async-validation-form.fixture.svelte +0 -19
  54. package/dist/components/forms/__tests__/async-validation-form.fixture.svelte.d.ts +0 -9
  55. package/dist/components/forms/__tests__/async-validation-form.fixture.svelte.d.ts.map +0 -1
  56. package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte +0 -21
  57. package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte.d.ts +0 -8
  58. package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte.d.ts.map +0 -1
  59. package/dist/components/forms/__tests__/composite-inputs-a11y.test.js +0 -69
  60. package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte +0 -27
  61. package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte.d.ts +0 -9
  62. package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte.d.ts.map +0 -1
  63. package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte +0 -48
  64. package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte.d.ts +0 -10
  65. package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte.d.ts.map +0 -1
  66. package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte +0 -26
  67. package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte.d.ts +0 -13
  68. package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte.d.ts.map +0 -1
  69. package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte +0 -52
  70. package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte.d.ts +0 -16
  71. package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte.d.ts.map +0 -1
  72. package/dist/components/forms/__tests__/form-scope.fixture.svelte +0 -30
  73. package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts +0 -10
  74. package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts.map +0 -1
  75. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte +0 -158
  76. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts +0 -51
  77. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts.map +0 -1
  78. package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte +0 -24
  79. package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte.d.ts +0 -11
  80. package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte.d.ts.map +0 -1
  81. package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte +0 -105
  82. package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte.d.ts +0 -42
  83. package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte.d.ts.map +0 -1
  84. package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte +0 -84
  85. package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts +0 -14
  86. package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts.map +0 -1
  87. package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte +0 -44
  88. package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte.d.ts +0 -9
  89. package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte.d.ts.map +0 -1
  90. package/dist/components/forms/__tests__/mic-keyboard-a11y.test.js +0 -74
  91. package/dist/components/forms/__tests__/policy-field.fixture.svelte +0 -35
  92. package/dist/components/forms/__tests__/policy-field.fixture.svelte.d.ts +0 -8
  93. package/dist/components/forms/__tests__/policy-field.fixture.svelte.d.ts.map +0 -1
  94. package/dist/components/forms/__tests__/rich-inputs-a11y.test.js +0 -87
  95. package/dist/components/settings/__tests__/SettingsCatalog.test.js +0 -69
  96. package/dist/components/settings/__tests__/catalog.test.js +0 -46
  97. package/dist/components/workspace/__tests__/AdminShell.responsive.test.js +0 -597
  98. package/dist/components/workspace/__tests__/AdminShell.test.js +0 -572
  99. package/dist/components/workspace/__tests__/RoleShell.test.js +0 -772
  100. package/dist/components/workspace/__tests__/TenantNav.test.js +0 -208
  101. package/dist/components/workspace/__tests__/WorkspaceAccountMenu.test.js +0 -79
  102. package/dist/components/workspace/__tests__/WorkspaceShell.test.js +0 -630
  103. package/dist/components/workspace/__tests__/admin-shell-header-overflow.test.js +0 -25
  104. package/dist/components/workspace/__tests__/admin-shell-hotkeys.test.js +0 -34
  105. package/dist/components/workspace/__tests__/admin-shell-mobile.test.js +0 -217
  106. package/dist/components/workspace/__tests__/admin-shell-mutation-effect-harness.svelte +0 -52
  107. package/dist/components/workspace/__tests__/admin-shell-mutation-effect-harness.svelte.d.ts +0 -23
  108. package/dist/components/workspace/__tests__/admin-shell-mutation-effect-harness.svelte.d.ts.map +0 -1
  109. package/dist/components/workspace/__tests__/admin-shell-page-contracts.test.js +0 -30
  110. package/dist/components/workspace/__tests__/admin-shell-responsive-state.test.js +0 -184
  111. package/dist/components/workspace/__tests__/admin-shell-settings.test.js +0 -25
  112. package/dist/components/workspace/__tests__/admin-shell-state.test.js +0 -286
  113. package/dist/components/workspace/__tests__/admin-shell-theme-hooks.test.js +0 -36
  114. package/dist/components/workspace/__tests__/breadcrumbs-helpers.test.js +0 -141
  115. package/dist/components/workspace/__tests__/context-forwarding-harness.svelte +0 -45
  116. package/dist/components/workspace/__tests__/context-forwarding-harness.svelte.d.ts +0 -21
  117. package/dist/components/workspace/__tests__/context-forwarding-harness.svelte.d.ts.map +0 -1
  118. package/dist/components/workspace/__tests__/define-tools-dock.test.js +0 -1188
  119. package/dist/components/workspace/__tests__/harness.svelte +0 -25
  120. package/dist/components/workspace/__tests__/harness.svelte.d.ts +0 -14
  121. package/dist/components/workspace/__tests__/harness.svelte.d.ts.map +0 -1
  122. package/dist/components/workspace/__tests__/index.test.js +0 -28
  123. package/dist/components/workspace/__tests__/legacy-index.test.js +0 -9
  124. package/dist/components/workspace/__tests__/manifest-nav-helpers.test.js +0 -24
  125. package/dist/components/workspace/__tests__/manifest-nav.test.js +0 -599
  126. package/dist/components/workspace/__tests__/nav-helpers.test.js +0 -95
  127. package/dist/components/workspace/__tests__/render-harness.svelte +0 -66
  128. package/dist/components/workspace/__tests__/render-harness.svelte.d.ts +0 -32
  129. package/dist/components/workspace/__tests__/render-harness.svelte.d.ts.map +0 -1
  130. package/dist/components/workspace/__tests__/render-tools-dock.test.js +0 -260
  131. package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte +0 -58
  132. package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte.d.ts +0 -16
  133. package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte.d.ts.map +0 -1
  134. package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte +0 -41
  135. package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte.d.ts +0 -13
  136. package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte.d.ts.map +0 -1
  137. package/dist/components/workspace/__tests__/shell-chrome-components.test.js +0 -108
  138. package/dist/components/workspace/__tests__/system-feed.test.js +0 -327
  139. package/dist/components/workspace/__tests__/test-icon.svelte +0 -17
  140. package/dist/components/workspace/__tests__/test-icon.svelte.d.ts +0 -19
  141. package/dist/components/workspace/__tests__/test-icon.svelte.d.ts.map +0 -1
  142. package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte +0 -38
  143. package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte.d.ts +0 -22
  144. package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte.d.ts.map +0 -1
  145. package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts +0 -66
  146. package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts.map +0 -1
  147. package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.js +0 -115
  148. package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.d.ts +0 -15
  149. package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.d.ts.map +0 -1
  150. package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.js +0 -7
  151. package/dist/components/workspace/__tests__/typed-tool-fixture.test.js +0 -115
  152. package/dist/components/workspace/__tests__/use-harness-orphan.svelte +0 -9
  153. package/dist/components/workspace/__tests__/use-harness-orphan.svelte.d.ts +0 -19
  154. package/dist/components/workspace/__tests__/use-harness-orphan.svelte.d.ts.map +0 -1
  155. package/dist/components/workspace/__tests__/use-harness.svelte +0 -23
  156. package/dist/components/workspace/__tests__/use-harness.svelte.d.ts +0 -8
  157. package/dist/components/workspace/__tests__/use-harness.svelte.d.ts.map +0 -1
  158. package/dist/components/workspace/__tests__/use-tools-dock.test.js +0 -33
  159. package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte +0 -43
  160. package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte.d.ts +0 -11
  161. package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte.d.ts.map +0 -1
  162. package/dist/components/workspace/server/__tests__/compose-availability.test.js +0 -383
  163. package/dist/components/workspace/server/__tests__/typed-context-fixture.d.ts +0 -78
  164. package/dist/components/workspace/server/__tests__/typed-context-fixture.d.ts.map +0 -1
  165. package/dist/components/workspace/server/__tests__/typed-context-fixture.js +0 -104
  166. package/dist/hooks/__tests__/stt-consumer.fixture.svelte +0 -25
  167. package/dist/hooks/__tests__/stt-consumer.fixture.svelte.d.ts +0 -15
  168. package/dist/hooks/__tests__/stt-consumer.fixture.svelte.d.ts.map +0 -1
  169. package/dist/hooks/__tests__/stt-ownership-harness.svelte +0 -42
  170. package/dist/hooks/__tests__/stt-ownership-harness.svelte.d.ts +0 -20
  171. package/dist/hooks/__tests__/stt-ownership-harness.svelte.d.ts.map +0 -1
  172. package/dist/hooks/__tests__/useSTT-ownership.test.js +0 -102
  173. package/dist/i18n/__tests__/server.spec.js +0 -57
  174. package/dist/internal/logger.test.js +0 -54
  175. package/dist/provider-mode.test.js +0 -86
  176. package/dist/provider-webmcp.test.js +0 -244
  177. package/dist/state/__tests__/app-state-ai-lifecycle.test.js +0 -240
  178. package/dist/state/__tests__/warm-clients.test.js +0 -40
  179. package/dist/web/__tests__/activity-feed-harness.svelte +0 -32
  180. package/dist/web/__tests__/activity-feed-harness.svelte.d.ts +0 -18
  181. package/dist/web/__tests__/activity-feed-harness.svelte.d.ts.map +0 -1
  182. package/dist/web/__tests__/activity-feed.integration.svelte.test.js +0 -190
  183. package/dist/web/__tests__/activity-feed.svelte.test.js +0 -235
  184. package/dist/web/__tests__/assistant-dock.integration.svelte.test.js +0 -204
  185. package/dist/web/__tests__/data-surface-conformance.integration.svelte.test.js +0 -1392
  186. package/dist/web/__tests__/harness.svelte +0 -25
  187. package/dist/web/__tests__/harness.svelte.d.ts +0 -15
  188. package/dist/web/__tests__/harness.svelte.d.ts.map +0 -1
  189. package/dist/web/__tests__/list-data-surface.fixture.svelte +0 -59
  190. package/dist/web/__tests__/list-data-surface.fixture.svelte.d.ts +0 -14
  191. package/dist/web/__tests__/list-data-surface.fixture.svelte.d.ts.map +0 -1
  192. package/dist/web/__tests__/list-data-surface.svelte.test.js +0 -106
  193. package/dist/web/__tests__/list-data-surface.test.js +0 -651
  194. package/dist/web/__tests__/list-sort-surface.fixture.svelte +0 -41
  195. package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts +0 -15
  196. package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts.map +0 -1
  197. package/dist/web/__tests__/list-sort-surface.svelte.test.js +0 -59
  198. package/dist/web/__tests__/live-collection.svelte.test.js +0 -185
  199. package/dist/web/__tests__/provider-ui-registry.fixture.svelte +0 -43
  200. package/dist/web/__tests__/provider-ui-registry.fixture.svelte.d.ts +0 -13
  201. package/dist/web/__tests__/provider-ui-registry.fixture.svelte.d.ts.map +0 -1
  202. package/dist/web/__tests__/provider-ui-registry.test.js +0 -235
  203. package/dist/web/__tests__/remote-query-harness.svelte +0 -27
  204. package/dist/web/__tests__/remote-query-harness.svelte.d.ts +0 -11
  205. package/dist/web/__tests__/remote-query-harness.svelte.d.ts.map +0 -1
  206. package/dist/web/__tests__/remote-query.svelte.test.js +0 -101
  207. package/dist/web/__tests__/surfaces.child.fixture.svelte +0 -69
  208. package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts +0 -16
  209. package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts.map +0 -1
  210. package/dist/web/__tests__/surfaces.fixture.svelte +0 -26
  211. package/dist/web/__tests__/surfaces.fixture.svelte.d.ts +0 -18
  212. package/dist/web/__tests__/surfaces.fixture.svelte.d.ts.map +0 -1
  213. package/dist/web/__tests__/surfaces.svelte.test.js +0 -140
  214. package/dist/web/__tests__/update-available-harness.svelte +0 -46
  215. package/dist/web/__tests__/update-available-harness.svelte.d.ts +0 -22
  216. package/dist/web/__tests__/update-available-harness.svelte.d.ts.map +0 -1
  217. package/dist/web/__tests__/update-available.svelte.test.js +0 -137
  218. package/dist/web/__tests__/view-intent-provider.fixture.svelte +0 -28
  219. package/dist/web/__tests__/view-intent-provider.fixture.svelte.d.ts +0 -13
  220. package/dist/web/__tests__/view-intent-provider.fixture.svelte.d.ts.map +0 -1
  221. package/dist/web/__tests__/view-intent.fixture.svelte +0 -33
  222. package/dist/web/__tests__/view-intent.fixture.svelte.d.ts +0 -15
  223. package/dist/web/__tests__/view-intent.fixture.svelte.d.ts.map +0 -1
  224. package/dist/web/__tests__/view-intent.intents.d.ts +0 -14
  225. package/dist/web/__tests__/view-intent.intents.d.ts.map +0 -1
  226. package/dist/web/__tests__/view-intent.intents.js +0 -38
  227. package/dist/web/__tests__/view-intent.svelte.test.js +0 -244
  228. package/dist/web/__tests__/webmcp-composed.child.fixture.svelte +0 -73
  229. package/dist/web/__tests__/webmcp-composed.child.fixture.svelte.d.ts +0 -8
  230. package/dist/web/__tests__/webmcp-composed.child.fixture.svelte.d.ts.map +0 -1
  231. package/dist/web/__tests__/webmcp-composed.fixture.svelte +0 -52
  232. package/dist/web/__tests__/webmcp-composed.fixture.svelte.d.ts +0 -12
  233. package/dist/web/__tests__/webmcp-composed.fixture.svelte.d.ts.map +0 -1
  234. package/dist/web/__tests__/webmcp-composed.integration.svelte.test.js +0 -251
  235. package/dist/web/__tests__/webmcp-effects-reactivity.child.fixture.svelte +0 -16
  236. package/dist/web/__tests__/webmcp-effects-reactivity.child.fixture.svelte.d.ts +0 -19
  237. package/dist/web/__tests__/webmcp-effects-reactivity.child.fixture.svelte.d.ts.map +0 -1
  238. package/dist/web/__tests__/webmcp-effects-reactivity.fixture.svelte +0 -11
  239. package/dist/web/__tests__/webmcp-effects-reactivity.fixture.svelte.d.ts +0 -8
  240. package/dist/web/__tests__/webmcp-effects-reactivity.fixture.svelte.d.ts.map +0 -1
  241. package/dist/web/__tests__/webmcp-harness-reactive-samename.svelte +0 -20
  242. package/dist/web/__tests__/webmcp-harness-reactive-samename.svelte.d.ts +0 -7
  243. package/dist/web/__tests__/webmcp-harness-reactive-samename.svelte.d.ts.map +0 -1
  244. package/dist/web/__tests__/webmcp-harness-unannotated.svelte +0 -14
  245. package/dist/web/__tests__/webmcp-harness-unannotated.svelte.d.ts +0 -19
  246. package/dist/web/__tests__/webmcp-harness-unannotated.svelte.d.ts.map +0 -1
  247. package/dist/web/__tests__/webmcp-harness.svelte +0 -18
  248. package/dist/web/__tests__/webmcp-harness.svelte.d.ts +0 -7
  249. package/dist/web/__tests__/webmcp-harness.svelte.d.ts.map +0 -1
  250. package/dist/web/__tests__/webmcp-ssr.fixture.svelte +0 -13
  251. package/dist/web/__tests__/webmcp-ssr.fixture.svelte.d.ts +0 -19
  252. package/dist/web/__tests__/webmcp-ssr.fixture.svelte.d.ts.map +0 -1
  253. package/dist/web/__tests__/webmcp-ui.test.js +0 -723
  254. package/dist/web/__tests__/webmcp.test.js +0 -234
@@ -1,723 +0,0 @@
1
- import { createDataSurfaceRegistry, } from '@happyvertical/smrt-ui/data';
2
- import { createControlInteractionRegistry, } from '@happyvertical/smrt-ui/forms';
3
- import { registerWebMcpBespokeTool, WebMcpToolNameCollisionError, } from '@happyvertical/smrt-web';
4
- import { isWebMcpProposalTool } from '@happyvertical/smrt-web/webmcp-tool-names';
5
- import { afterEach, describe, expect, it, vi } from 'vitest';
6
- import { registerWebMcpUiTools } from '../webmcp-ui.js';
7
- function modelContext() {
8
- const registered = [];
9
- const signals = [];
10
- const document = {
11
- modelContext: {
12
- async registerTool(tool, options) {
13
- registered.push(tool);
14
- if (options?.signal)
15
- signals.push(options.signal);
16
- },
17
- },
18
- };
19
- return { document, registered, signals };
20
- }
21
- function parse(value) {
22
- return Promise.resolve(value).then((result) => JSON.parse(result));
23
- }
24
- function findTool(tools, name) {
25
- const tool = tools.find((candidate) => candidate.name === name);
26
- if (!tool)
27
- throw new Error(`Missing tool: ${name}`);
28
- return tool;
29
- }
30
- function descriptor() {
31
- return {
32
- version: 1,
33
- identity: { surfaceId: 'content', kind: 'table' },
34
- schemaVersion: 1,
35
- label: 'Content',
36
- rowKey: 'id',
37
- columns: [
38
- {
39
- id: 'id',
40
- label: 'ID',
41
- capabilities: ['read', 'project'],
42
- },
43
- {
44
- id: 'internal',
45
- label: 'Internal',
46
- visibility: 'hidden',
47
- capabilities: ['read', 'project'],
48
- },
49
- ],
50
- query: {
51
- modes: ['rows'],
52
- projectableColumnIds: ['id', 'internal'],
53
- },
54
- controls: [{ id: 'next-page', label: 'Next page' }],
55
- actions: [],
56
- limits: { maxQueryRows: 50, maxQueryBytes: 10_000, maxSelectionSize: 10 },
57
- };
58
- }
59
- describe('registerWebMcpUiTools', () => {
60
- it('registers one fixed tool set and resolves mounted controls dynamically', async () => {
61
- const browser = modelContext();
62
- const controls = createControlInteractionRegistry();
63
- const surfaces = createDataSurfaceRegistry();
64
- const dispose = registerWebMcpUiTools({
65
- controlRegistry: controls,
66
- dataSurfaceRegistry: surfaces,
67
- document: browser.document,
68
- });
69
- expect(browser.registered.map((tool) => tool.name)).toEqual([
70
- 'smrt_ui_list_form_controls',
71
- 'smrt_ui_inspect_form_control',
72
- 'smrt_ui_execute_form_control',
73
- 'smrt_ui_list_data_surfaces',
74
- 'smrt_ui_inspect_data_surface',
75
- 'smrt_ui_execute_data_surface_control',
76
- ]);
77
- expect(browser.registered).toHaveLength(6);
78
- const list = findTool(browser.registered, 'smrt_ui_list_form_controls');
79
- expect(await parse(list.execute({}))).toEqual({ ok: true, result: [] });
80
- let value = 'Ada';
81
- const unregister = controls.register({
82
- identity: { formId: 'profile', controlId: 'name' },
83
- metadata: { kind: 'text', label: 'Name' },
84
- getValue: () => value,
85
- setValue: (next) => {
86
- value = String(next);
87
- },
88
- focus: vi.fn(),
89
- });
90
- expect((await parse(list.execute({}))).result).toHaveLength(1);
91
- expect(browser.registered).toHaveLength(6);
92
- unregister();
93
- expect(await parse(list.execute({}))).toEqual({ ok: true, result: [] });
94
- dispose();
95
- expect(browser.signals).toHaveLength(6);
96
- expect(browser.signals.every((signal) => signal.aborted)).toBe(true);
97
- });
98
- it('uses agent consent semantics and cannot be tricked into confirmation', async () => {
99
- const browser = modelContext();
100
- const controls = createControlInteractionRegistry();
101
- let value = 'Ada';
102
- controls.register({
103
- identity: { formId: 'profile', controlId: 'name' },
104
- metadata: { kind: 'text', label: 'Name' },
105
- getValue: () => value,
106
- setValue: (next) => {
107
- value = String(next);
108
- },
109
- });
110
- registerWebMcpUiTools({
111
- controlRegistry: controls,
112
- dataSurfaceRegistry: createDataSurfaceRegistry(),
113
- document: browser.document,
114
- });
115
- const execute = findTool(browser.registered, 'smrt_ui_execute_form_control');
116
- const identity = { formId: 'profile', controlId: 'name' };
117
- expect((await parse(execute.execute({ action: 'stage', identity, value: 'Grace' }))).result).toMatchObject({ ok: true, action: 'stage' });
118
- expect(value).toBe('Ada');
119
- expect((await parse(execute.execute({ action: 'apply', identity }))).result).toMatchObject({ ok: false, reason: 'human_confirmation_required' });
120
- expect(value).toBe('Ada');
121
- expect(await parse(execute.execute({
122
- action: 'apply',
123
- identity,
124
- confirmed: true,
125
- }))).toEqual({ ok: false, reason: 'invalid_request', details: 'confirmed' });
126
- expect(value).toBe('Ada');
127
- });
128
- it('returns the canonical values staged by general form-control tools', async () => {
129
- const browser = modelContext();
130
- const controls = createControlInteractionRegistry();
131
- const identities = {
132
- checkbox: { formId: 'base', controlId: 'enabled' },
133
- number: { formId: 'base', controlId: 'count' },
134
- option: { formId: 'base', controlId: 'role' },
135
- };
136
- controls.register({
137
- identity: identities.checkbox,
138
- metadata: { kind: 'checkbox' },
139
- getValue: () => false,
140
- setValue: () => undefined,
141
- prepareValue: (next) => {
142
- if (typeof next !== 'boolean')
143
- throw new Error('staged_value_invalid');
144
- return next;
145
- },
146
- });
147
- controls.register({
148
- identity: identities.number,
149
- metadata: { kind: 'number' },
150
- getValue: () => 0,
151
- setValue: () => undefined,
152
- prepareValue: (next) => Number(next),
153
- });
154
- controls.register({
155
- identity: identities.option,
156
- metadata: {
157
- kind: 'listbox',
158
- options: [{ value: 1, label: 'Admin' }],
159
- },
160
- getValue: () => 1,
161
- setValue: () => undefined,
162
- prepareValue: (next) => {
163
- if (String(next) !== '1')
164
- throw new Error('staged_value_invalid');
165
- return 1;
166
- },
167
- });
168
- registerWebMcpUiTools({
169
- controlRegistry: controls,
170
- dataSurfaceRegistry: createDataSurfaceRegistry(),
171
- document: browser.document,
172
- });
173
- const execute = findTool(browser.registered, 'smrt_ui_execute_form_control');
174
- expect(await parse(execute.execute({
175
- action: 'stage',
176
- identity: identities.checkbox,
177
- value: {},
178
- }))).toMatchObject({
179
- ok: true,
180
- result: { ok: false, reason: 'execution_failed' },
181
- });
182
- expect(controls.get(identities.checkbox)?.state.staged).toBeUndefined();
183
- for (const [identity, value, staged] of [
184
- [identities.number, '42', 42],
185
- [identities.option, '1', 1],
186
- ]) {
187
- const response = await parse(execute.execute({ action: 'stage', identity, value }));
188
- expect(response).toMatchObject({ ok: true, result: { ok: true } });
189
- expect(response.result.snapshot.state.staged.value).toEqual(staged);
190
- }
191
- });
192
- it('keeps secret values redacted from list, inspect, and execute responses', async () => {
193
- const browser = modelContext();
194
- const controls = createControlInteractionRegistry();
195
- controls.register({
196
- identity: { formId: 'account', controlId: 'password' },
197
- metadata: {
198
- kind: 'password',
199
- label: 'Password',
200
- sensitivity: 'secret',
201
- },
202
- getValue: () => 'never-serialize-this',
203
- setValue: () => { },
204
- getState: () => ({
205
- valid: false,
206
- validationMessage: 'never-serialize-this is invalid',
207
- }),
208
- focus: () => {
209
- throw new Error('never-serialize-this cannot be focused');
210
- },
211
- // An agent's `focus` reveals instead of moving keyboard focus.
212
- reveal: () => {
213
- throw new Error('never-serialize-this cannot be revealed');
214
- },
215
- });
216
- registerWebMcpUiTools({
217
- controlRegistry: controls,
218
- dataSurfaceRegistry: createDataSurfaceRegistry(),
219
- document: browser.document,
220
- });
221
- const list = findTool(browser.registered, 'smrt_ui_list_form_controls');
222
- const inspect = findTool(browser.registered, 'smrt_ui_inspect_form_control');
223
- const execute = findTool(browser.registered, 'smrt_ui_execute_form_control');
224
- const listed = await parse(list.execute({ formId: 'account' }));
225
- const inspected = await parse(inspect.execute({
226
- identity: { formId: 'account', controlId: 'password' },
227
- }));
228
- const executed = await parse(execute.execute({
229
- action: 'focus',
230
- identity: { formId: 'account', controlId: 'password' },
231
- }));
232
- expect(JSON.stringify([listed, inspected, executed])).not.toContain('never-serialize-this');
233
- expect(listed.result[0].state.valueRedacted).toBe(true);
234
- expect(inspected.result.state.valueRedacted).toBe(true);
235
- expect(executed.result.snapshot.state.valueRedacted).toBe(true);
236
- expect(executed.result.snapshot.state).not.toHaveProperty('validationMessage');
237
- expect(executed.result.reason).toBe('execution_failed');
238
- });
239
- it('does not expose host error messages that mimic public failures', async () => {
240
- const browser = modelContext();
241
- const controls = createControlInteractionRegistry();
242
- controls.register({
243
- identity: { formId: 'profile', controlId: 'unstable' },
244
- metadata: { kind: 'text', label: 'Unstable' },
245
- getValue: () => {
246
- throw new Error('not_found: private customer 4711');
247
- },
248
- });
249
- registerWebMcpUiTools({
250
- controlRegistry: controls,
251
- dataSurfaceRegistry: createDataSurfaceRegistry(),
252
- document: browser.document,
253
- });
254
- const inspect = findTool(browser.registered, 'smrt_ui_inspect_form_control');
255
- const result = await parse(inspect.execute({
256
- identity: { formId: 'profile', controlId: 'unstable' },
257
- }));
258
- expect(result).toEqual({ ok: false, reason: 'execution_failed' });
259
- expect(JSON.stringify(result)).not.toContain('customer 4711');
260
- });
261
- it('redacts sensitive injected snapshots and result text despite inconsistent flags', async () => {
262
- for (const sensitivity of ['secret', 'sensitive']) {
263
- const marker = `injected-${sensitivity}-value`;
264
- const browser = modelContext();
265
- const snapshot = {
266
- identity: { formId: 'injected', controlId: sensitivity },
267
- metadata: {
268
- kind: 'password',
269
- sensitivity,
270
- capabilities: ['discard'],
271
- },
272
- state: {
273
- value: marker,
274
- valueRedacted: false,
275
- stagedValue: `${marker}-staged`,
276
- stagedValueRedacted: false,
277
- validationMessage: `${marker} validation`,
278
- staged: {
279
- value: `${marker}-nested`,
280
- valueRedacted: false,
281
- provenance: { source: 'agent' },
282
- stagedAt: 1,
283
- revision: 1,
284
- stale: false,
285
- validationMessage: `${marker} nested validation`,
286
- },
287
- },
288
- };
289
- const controls = {
290
- register: () => () => { },
291
- unregister: () => { },
292
- list: () => [snapshot],
293
- get: () => snapshot,
294
- execute: async (command) => ({
295
- ok: false,
296
- action: command.action,
297
- identity: command.identity,
298
- reason: `${marker} reason`,
299
- validationMessage: `${marker} result validation`,
300
- snapshot,
301
- }),
302
- subscribe: () => () => { },
303
- };
304
- registerWebMcpUiTools({
305
- controlRegistry: controls,
306
- dataSurfaceRegistry: createDataSurfaceRegistry(),
307
- document: browser.document,
308
- });
309
- const identity = { formId: 'injected', controlId: sensitivity };
310
- const list = await parse(findTool(browser.registered, 'smrt_ui_list_form_controls').execute({}));
311
- const inspect = await parse(findTool(browser.registered, 'smrt_ui_inspect_form_control').execute({
312
- identity,
313
- }));
314
- const executed = await parse(findTool(browser.registered, 'smrt_ui_execute_form_control').execute({
315
- action: 'focus',
316
- identity,
317
- }));
318
- expect(JSON.stringify([list, inspect, executed])).not.toContain(marker);
319
- for (const result of [
320
- list.result[0],
321
- inspect.result,
322
- executed.result.snapshot,
323
- ]) {
324
- expect(result.metadata.capabilities).toEqual(['discard']);
325
- expect(result.state).toMatchObject({
326
- valueRedacted: true,
327
- stagedValueRedacted: true,
328
- staged: {
329
- valueRedacted: true,
330
- },
331
- });
332
- expect(result.state).not.toHaveProperty('value');
333
- expect(result.state).not.toHaveProperty('stagedValue');
334
- expect(result.state).not.toHaveProperty('validationMessage');
335
- expect(result.state.staged).not.toHaveProperty('value');
336
- expect(result.state.staged).not.toHaveProperty('validationMessage');
337
- }
338
- expect(executed.result.reason).toBe('execution_failed');
339
- expect(executed.result).not.toHaveProperty('validationMessage');
340
- }
341
- });
342
- it('filters hidden columns and preserves surface revision and replay failures', async () => {
343
- const browser = modelContext();
344
- const surfaces = createDataSurfaceRegistry();
345
- let revision = 2;
346
- let page = 1;
347
- surfaces.register({
348
- descriptor: descriptor(),
349
- getSnapshot: () => ({
350
- revision,
351
- state: {
352
- page,
353
- internal: 'never-serialize-this',
354
- table: {
355
- version: 3,
356
- state: {
357
- filters: [
358
- { columnId: 'internal', value: 'nested-hidden-filter' },
359
- { columnId: 'id', value: ['internal'] },
360
- ],
361
- sorting: [{ columnId: 'internal', direction: 'asc' }],
362
- columnOrder: ['id', 'internal'],
363
- },
364
- },
365
- },
366
- }),
367
- execute: (_command) => {
368
- page += 1;
369
- revision += 1;
370
- },
371
- });
372
- registerWebMcpUiTools({
373
- controlRegistry: createControlInteractionRegistry(),
374
- dataSurfaceRegistry: surfaces,
375
- document: browser.document,
376
- });
377
- const list = findTool(browser.registered, 'smrt_ui_list_data_surfaces');
378
- const inspect = findTool(browser.registered, 'smrt_ui_inspect_data_surface');
379
- const execute = findTool(browser.registered, 'smrt_ui_execute_data_surface_control');
380
- const identity = { surfaceId: 'content', kind: 'table' };
381
- expect((await parse(list.execute({}))).result[0].columns).toHaveLength(1);
382
- const snapshot = (await parse(inspect.execute({ identity }))).result;
383
- expect(snapshot.descriptor.query.projectableColumnIds).toEqual(['id']);
384
- expect(snapshot.state).not.toHaveProperty('internal');
385
- expect(JSON.stringify(snapshot)).not.toContain('nested-hidden-filter');
386
- expect(snapshot.state.table.state.columnOrder).toEqual(['id']);
387
- expect(snapshot.state.table.state.filters).toEqual([
388
- { columnId: 'id', value: ['internal'] },
389
- ]);
390
- const hiddenRowKeyDescriptor = descriptor();
391
- hiddenRowKeyDescriptor.rowKey = 'internal';
392
- const hiddenRowKeySurfaces = createDataSurfaceRegistry();
393
- hiddenRowKeySurfaces.register({
394
- descriptor: hiddenRowKeyDescriptor,
395
- getSnapshot: () => ({
396
- revision: 0,
397
- state: {
398
- table: {
399
- state: {
400
- selection: {
401
- scope: 'explicit',
402
- rowIds: ['nested-private-row-id'],
403
- },
404
- selectedRowIds: ['nested-private-row-id'],
405
- expandedRowIds: ['nested-private-row-id'],
406
- },
407
- },
408
- },
409
- selection: { scope: 'explicit-ids', rowIds: ['private-row-id'] },
410
- }),
411
- });
412
- const hiddenRowKeyBrowser = modelContext();
413
- registerWebMcpUiTools({
414
- controlRegistry: createControlInteractionRegistry(),
415
- dataSurfaceRegistry: hiddenRowKeySurfaces,
416
- document: hiddenRowKeyBrowser.document,
417
- });
418
- const hiddenRowKeyList = await parse(findTool(hiddenRowKeyBrowser.registered, 'smrt_ui_list_data_surfaces').execute({}));
419
- expect(hiddenRowKeyList.result[0]).not.toHaveProperty('rowKey');
420
- const hiddenRowKeyInspect = await parse(findTool(hiddenRowKeyBrowser.registered, 'smrt_ui_inspect_data_surface').execute({ identity }));
421
- expect(hiddenRowKeyInspect.result.selection).toBeNull();
422
- expect(JSON.stringify(hiddenRowKeyInspect)).not.toContain('private-row-id');
423
- expect(JSON.stringify(hiddenRowKeyInspect)).not.toContain('nested-private-row-id');
424
- const command = {
425
- version: 1,
426
- commandId: 'page-1',
427
- identity,
428
- expectedRevision: 2,
429
- controlId: 'next-page',
430
- };
431
- const completed = (await parse(execute.execute(command))).result;
432
- expect(completed).toMatchObject({
433
- ok: true,
434
- revision: 3,
435
- });
436
- expect(completed.snapshot.descriptor.columns).toHaveLength(1);
437
- expect(completed.snapshot.state).not.toHaveProperty('internal');
438
- expect((await parse(execute.execute(command))).result).toMatchObject({
439
- ok: true,
440
- revision: 3,
441
- });
442
- expect((await parse(execute.execute({ ...command, controlId: 'different-control' }))).result).toMatchObject({ ok: false, reason: 'idempotency_conflict' });
443
- const stale = (await parse(execute.execute({ ...command, commandId: 'page-2' }))).result;
444
- expect(stale).toMatchObject({ ok: false, reason: 'stale_revision' });
445
- expect(stale.snapshot.descriptor.columns).toHaveLength(1);
446
- });
447
- it('rejects invalid and oversized requests with distinct failures', async () => {
448
- const browser = modelContext();
449
- const controls = createControlInteractionRegistry();
450
- registerWebMcpUiTools({
451
- controlRegistry: controls,
452
- dataSurfaceRegistry: createDataSurfaceRegistry(),
453
- document: browser.document,
454
- });
455
- const inspect = findTool(browser.registered, 'smrt_ui_inspect_form_control');
456
- expect(await parse(inspect.execute({ identity: { formId: '', controlId: 'name' } }))).toEqual({
457
- ok: false,
458
- reason: 'invalid_identifier',
459
- details: 'formId',
460
- });
461
- expect(await parse(inspect.execute({ payload: 'x'.repeat(100_001) }))).toEqual({ ok: false, reason: 'limit_exceeded' });
462
- controls.register({
463
- identity: { formId: 'broken', controlId: 'value' },
464
- metadata: { kind: 'text' },
465
- getValue: () => {
466
- throw new Error('private host detail');
467
- },
468
- });
469
- const list = findTool(browser.registered, 'smrt_ui_list_form_controls');
470
- const failed = await parse(list.execute({ formId: 'broken' }));
471
- expect(failed).toEqual({ ok: false, reason: 'execution_failed' });
472
- expect(JSON.stringify(failed)).not.toContain('private host detail');
473
- });
474
- it('locks a prefix atomically, permits distinct prefixes, and no-ops without WebMCP', () => {
475
- const browser = modelContext();
476
- const registries = {
477
- controlRegistry: createControlInteractionRegistry(),
478
- dataSurfaceRegistry: createDataSurfaceRegistry(),
479
- };
480
- const dispose = registerWebMcpUiTools({
481
- ...registries,
482
- document: browser.document,
483
- });
484
- expect(() => registerWebMcpUiTools({ ...registries, document: browser.document })).toThrow('already registered');
485
- expect(browser.registered).toHaveLength(6);
486
- const disposeOther = registerWebMcpUiTools({
487
- ...registries,
488
- prefix: 'other_',
489
- document: browser.document,
490
- });
491
- expect(browser.registered).toHaveLength(12);
492
- disposeOther();
493
- dispose();
494
- expect(() => registerWebMcpUiTools({
495
- ...registries,
496
- document: {},
497
- })).not.toThrow();
498
- });
499
- it('aborts every partial registration and releases the lock when a host rejects a tool', () => {
500
- const signals = [];
501
- let calls = 0;
502
- const document = {
503
- modelContext: {
504
- registerTool(_tool, options) {
505
- calls += 1;
506
- if (options?.signal)
507
- signals.push(options.signal);
508
- if (calls === 3)
509
- throw new Error('host collision');
510
- },
511
- },
512
- };
513
- const registries = {
514
- controlRegistry: createControlInteractionRegistry(),
515
- dataSurfaceRegistry: createDataSurfaceRegistry(),
516
- };
517
- expect(() => registerWebMcpUiTools({ ...registries, document })).toThrow('host collision');
518
- expect(signals.every((signal) => signal.aborted)).toBe(true);
519
- calls = 0;
520
- document.modelContext.registerTool = (_tool, options) => {
521
- calls += 1;
522
- if (options?.signal)
523
- signals.push(options.signal);
524
- };
525
- expect(() => registerWebMcpUiTools({ ...registries, document })).not.toThrow();
526
- expect(calls).toBe(6);
527
- });
528
- it('observes asynchronous host rejection, aborts tools, and releases only its own lock', async () => {
529
- let rejectFirstRegistration;
530
- const signals = [];
531
- let calls = 0;
532
- const document = {
533
- modelContext: {
534
- registerTool(_tool, options) {
535
- calls += 1;
536
- if (options?.signal)
537
- signals.push(options.signal);
538
- if (calls === 1) {
539
- return new Promise((_resolve, reject) => {
540
- rejectFirstRegistration = reject;
541
- });
542
- }
543
- },
544
- },
545
- };
546
- const registries = {
547
- controlRegistry: createControlInteractionRegistry(),
548
- dataSurfaceRegistry: createDataSurfaceRegistry(),
549
- };
550
- const disposeFirst = registerWebMcpUiTools({ ...registries, document });
551
- disposeFirst();
552
- const disposeReplacement = registerWebMcpUiTools({
553
- ...registries,
554
- document,
555
- });
556
- rejectFirstRegistration?.(new Error('late host collision'));
557
- await Promise.resolve();
558
- await Promise.resolve();
559
- expect(signals.slice(0, 6).every((signal) => signal.aborted)).toBe(true);
560
- expect(signals.slice(6).every((signal) => !signal.aborted)).toBe(true);
561
- expect(() => registerWebMcpUiTools({ ...registries, document })).toThrow('already registered');
562
- disposeReplacement();
563
- expect(() => registerWebMcpUiTools({ ...registries, document })).not.toThrow();
564
- });
565
- it('releases an active prefix when asynchronous host registration fails', async () => {
566
- const signals = [];
567
- let rejectRegistration;
568
- const document = {
569
- modelContext: {
570
- registerTool(_tool, options) {
571
- if (options?.signal)
572
- signals.push(options.signal);
573
- if (!rejectRegistration) {
574
- return new Promise((_resolve, reject) => {
575
- rejectRegistration = reject;
576
- });
577
- }
578
- },
579
- },
580
- };
581
- const registries = {
582
- controlRegistry: createControlInteractionRegistry(),
583
- dataSurfaceRegistry: createDataSurfaceRegistry(),
584
- };
585
- registerWebMcpUiTools({ ...registries, document });
586
- rejectRegistration?.(new Error('host collision'));
587
- await Promise.resolve();
588
- await Promise.resolve();
589
- expect(signals.every((signal) => signal.aborted)).toBe(true);
590
- expect(() => registerWebMcpUiTools({ ...registries, document })).not.toThrow();
591
- });
592
- });
593
- /**
594
- * The fixed UI tools and a hand-written `useWebMcpTool` tool are derived on
595
- * different paths and neither can see the other's runtime prefix, so before
596
- * #2613 a bespoke name matching a custom-prefixed UI tool reached the host and
597
- * one of the two was silently lost. Both paths now reserve through the
598
- * document-global tool-name lock. These use the real jsdom `document` because
599
- * that is the object both registrars key their reservations on in a page —
600
- * `registerWebMcpBespokeTool` reads `globalThis.document` and has no injection
601
- * seam.
602
- */
603
- describe('cross-path tool-name lock (#2613)', () => {
604
- const registries = () => ({
605
- controlRegistry: createControlInteractionRegistry(),
606
- dataSurfaceRegistry: createDataSurfaceRegistry(),
607
- });
608
- function installPageModelContext() {
609
- const registered = [];
610
- document.modelContext = {
611
- async registerTool(tool) {
612
- registered.push(tool);
613
- },
614
- };
615
- return registered;
616
- }
617
- function bespoke(name) {
618
- return {
619
- name,
620
- description: 'A component-owned tool',
621
- inputSchema: { type: 'object', properties: {} },
622
- annotations: {
623
- readOnlyHint: true,
624
- idempotentHint: true,
625
- openWorldHint: false,
626
- },
627
- execute: () => 'ok',
628
- };
629
- }
630
- afterEach(() => {
631
- document.modelContext = undefined;
632
- });
633
- it('refuses a bespoke tool named for a UI tool under a custom prefix', () => {
634
- const registered = installPageModelContext();
635
- const dispose = registerWebMcpUiTools({
636
- ...registries(),
637
- prefix: 'agent_ui_',
638
- });
639
- expect(registered).toHaveLength(6);
640
- let thrown;
641
- try {
642
- registerWebMcpBespokeTool(bespoke('agent_ui_execute_form_control'));
643
- }
644
- catch (error) {
645
- thrown = error;
646
- }
647
- expect(thrown).toBeInstanceOf(WebMcpToolNameCollisionError);
648
- const collision = thrown;
649
- expect(collision.toolName).toBe('agent_ui_execute_form_control');
650
- expect(collision.owner).toBe('ui');
651
- expect(collision.requestedBy).toBe('bespoke');
652
- expect(registered).toHaveLength(6);
653
- // The default prefix was never mounted, so the same suffix is free there.
654
- const other = registerWebMcpBespokeTool(bespoke('smrt_ui_execute_form_control'));
655
- expect(registered).toHaveLength(7);
656
- other();
657
- dispose();
658
- });
659
- it('refuses a UI prefix a live bespoke tool already occupies', () => {
660
- const registered = installPageModelContext();
661
- const held = registerWebMcpBespokeTool(bespoke('agent_ui_inspect_form_control'));
662
- expect(registered).toHaveLength(1);
663
- let thrown;
664
- try {
665
- registerWebMcpUiTools({ ...registries(), prefix: 'agent_ui_' });
666
- }
667
- catch (error) {
668
- thrown = error;
669
- }
670
- expect(thrown).toBeInstanceOf(WebMcpToolNameCollisionError);
671
- expect(thrown.owner).toBe('bespoke');
672
- // Atomic: not one of the six reached the host.
673
- expect(registered).toHaveLength(1);
674
- // And the refused mount released its own prefix lock, so the same prefix
675
- // works once the bespoke tool goes away.
676
- held();
677
- const dispose = registerWebMcpUiTools({
678
- ...registries(),
679
- prefix: 'agent_ui_',
680
- });
681
- expect(registered).toHaveLength(7);
682
- dispose();
683
- });
684
- it('frees the prefix and the names when the host swaps its model context', () => {
685
- const registered = installPageModelContext();
686
- // Deliberately never disposed: the adapter's six tools live in the old
687
- // registry, which the host is about to throw away. Both the prefix lock
688
- // and the name reservations must let go with it, or the same prefix stays
689
- // permanently refused against a registry that never held it.
690
- registerWebMcpUiTools({ ...registries(), prefix: 'agent_ui_' });
691
- expect(registered).toHaveLength(6);
692
- const rebound = installPageModelContext();
693
- const dispose = registerWebMcpUiTools({
694
- ...registries(),
695
- prefix: 'agent_ui_',
696
- });
697
- expect(rebound).toHaveLength(6);
698
- dispose();
699
- });
700
- it('frees the six names on dispose so a remount succeeds', () => {
701
- const registered = installPageModelContext();
702
- for (let cycle = 0; cycle < 3; cycle += 1) {
703
- registerWebMcpUiTools({ ...registries(), prefix: 'agent_ui_' })();
704
- }
705
- expect(registered).toHaveLength(18);
706
- const bespokeDisposer = registerWebMcpBespokeTool(bespoke('agent_ui_execute_form_control'));
707
- expect(registered).toHaveLength(19);
708
- bespokeDisposer();
709
- });
710
- it('brands every fixed UI tool as proposal-only for the in-page assistant', () => {
711
- const browser = modelContext();
712
- const dispose = registerWebMcpUiTools({
713
- controlRegistry: createControlInteractionRegistry(),
714
- dataSurfaceRegistry: createDataSurfaceRegistry(),
715
- document: browser.document,
716
- });
717
- expect(browser.registered).toHaveLength(6);
718
- for (const tool of browser.registered) {
719
- expect(isWebMcpProposalTool(tool.execute)).toBe(true);
720
- }
721
- dispose();
722
- });
723
- });