@kitn.ai/ui 0.25.2 → 0.26.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 (409) hide show
  1. package/README.md +2 -2
  2. package/dist/components/attachment-types.d.ts +46 -1
  3. package/dist/components/attachments.d.ts +31 -0
  4. package/dist/components/audio-visualizer/fit-scale.d.ts +50 -0
  5. package/dist/components/audio-visualizer/index.d.ts +15 -1
  6. package/dist/components/audio-visualizer/variant-bar.d.ts +24 -1
  7. package/dist/components/chat-thread.d.ts +3 -41
  8. package/dist/components/code-block.d.ts +22 -0
  9. package/dist/components/conversation-item.d.ts +54 -1
  10. package/dist/components/conversation-list.d.ts +65 -0
  11. package/dist/components/form.d.ts +42 -0
  12. package/dist/components/message.d.ts +7 -0
  13. package/dist/components/toast.d.ts +2 -1
  14. package/dist/components/voice-input.d.ts +8 -0
  15. package/dist/components/voice-output.d.ts +12 -1
  16. package/dist/components/workspace-shell.d.ts +87 -0
  17. package/dist/{create-tween-B5Y4Im8G.js → create-tween-BFT6c-Y8.js} +1 -1
  18. package/dist/{create-tween-4z1XYLkR.js → create-tween-CJNlzoaO.js} +1 -1
  19. package/dist/{create-tween-CUzOlaMS.js → create-tween-CQED4T0g.js} +1 -1
  20. package/dist/custom-elements.json +2931 -2572
  21. package/dist/diagnostics/hook.d.ts +81 -0
  22. package/dist/diagnostics/index.d.ts +7 -0
  23. package/dist/diagnostics/report-request.d.ts +61 -0
  24. package/dist/diagnostics.d.ts +5 -0
  25. package/dist/diagnostics.js +209 -0
  26. package/dist/elements/agent-card.js +1 -1
  27. package/dist/elements/artifact.js +1 -1
  28. package/dist/elements/attachments.js +1 -1
  29. package/dist/elements/audio-visualizer.js +1 -1
  30. package/dist/elements/autoloader.js +1 -1
  31. package/dist/elements/avatar.js +1 -1
  32. package/dist/elements/badge.js +1 -1
  33. package/dist/elements/button.js +1 -1
  34. package/dist/elements/card.js +1 -1
  35. package/dist/elements/cards.js +1 -1
  36. package/dist/elements/chain-of-thought.js +1 -1
  37. package/dist/elements/chat-scope-picker.js +1 -1
  38. package/dist/elements/chat-workspace.js +1 -1
  39. package/dist/elements/chat.js +1 -1
  40. package/dist/elements/checkpoint.js +1 -1
  41. package/dist/elements/choice.js +1 -1
  42. package/dist/elements/chunks/{Icon-b5A8hlHs.js → Icon--Y8vsFal.js} +1 -1
  43. package/dist/elements/chunks/action-icons-D9u3OzYW.js +1 -0
  44. package/dist/elements/chunks/arrow-left-CVh1TeN7.js +1 -0
  45. package/dist/elements/chunks/{artifact-BSuPYFHY.js → artifact-BkYcfIlZ.js} +1 -1
  46. package/dist/elements/chunks/attachments-DwfNlubq.js +1 -0
  47. package/dist/elements/chunks/audio-visualizer-D7lB6mvR.js +1 -0
  48. package/dist/elements/chunks/{badge-TIMiJFKc.js → badge-Q0-Cmwzz.js} +1 -1
  49. package/dist/elements/chunks/{button-B9okqG73.js → button-flUbeufF.js} +1 -1
  50. package/dist/elements/chunks/{card-renderer-CSoTL_e1.js → card-renderer-3Dyl-FpN.js} +1 -1
  51. package/dist/elements/chunks/check-B5kBPKeo.js +1 -0
  52. package/dist/elements/chunks/chevron-down-DZ1GS39h.js +1 -0
  53. package/dist/elements/chunks/chevron-right-CvP7OUd2.js +1 -0
  54. package/dist/elements/chunks/{choice-card-CGMc3kek.js → choice-card-D4QUIOWZ.js} +1 -1
  55. package/dist/elements/chunks/circle-CDf9g6MQ.js +1 -0
  56. package/dist/elements/chunks/circle-check-CA47Nqbz.js +1 -0
  57. package/dist/elements/chunks/{circle-x-mhH7pOK2.js → circle-x-CJ_HVbC8.js} +1 -1
  58. package/dist/elements/chunks/code-block-BdLiTitd.js +1 -0
  59. package/dist/elements/chunks/collapsible-BzbXpPzr.js +1 -0
  60. package/dist/elements/chunks/{composer-CZZmtYOT.js → composer-Byve-n8K.js} +2 -2
  61. package/dist/elements/chunks/{confirm-card-BWjf2-xo.js → confirm-card-B0flE3MN.js} +1 -1
  62. package/dist/elements/chunks/context-VLIL3sJ-.js +1 -0
  63. package/dist/elements/chunks/controllable-DTwTIlwp.js +1 -0
  64. package/dist/elements/chunks/conversation-item-BDztezOG.js +1 -0
  65. package/dist/elements/chunks/copy-Nf-kqxXH.js +1 -0
  66. package/dist/elements/chunks/{create-tween-CUrM6D_o.js → create-tween-c4Z8TDYz.js} +2 -2
  67. package/dist/elements/chunks/default-input-C2iLFq0b.js +1 -0
  68. package/dist/elements/chunks/define-wjHR1MkU.js +1 -0
  69. package/dist/elements/chunks/disclosure-CcNbyLy0.js +1 -0
  70. package/dist/elements/chunks/{download-DnCyWFJE.js → download-C4V7tBdb.js} +1 -1
  71. package/dist/elements/chunks/dropdown-BgWTthUx.js +1 -0
  72. package/dist/elements/chunks/{ellipsis-CVxqfsDZ.js → ellipsis-7gU33mL2.js} +1 -1
  73. package/dist/elements/chunks/embed-D3KewsIJ.js +1 -0
  74. package/dist/elements/chunks/{external-link-DG2sRdQL.js → external-link-CSOEsNlx.js} +1 -1
  75. package/dist/elements/chunks/{file-text-D0J9F6M7.js → file-text-D1G8khle.js} +1 -1
  76. package/dist/elements/chunks/{file-tree-yGWNK0Js.js → file-tree-zvB2i7P9.js} +1 -1
  77. package/dist/elements/chunks/{folder-NV1pi_ud.js → folder-WW2LGLid.js} +1 -1
  78. package/dist/elements/chunks/form-CQirdC4W.js +1 -0
  79. package/dist/elements/chunks/hover-card-CJJkfHTK.js +1 -0
  80. package/dist/elements/chunks/icon-Cfgmf4fa.js +1 -0
  81. package/dist/elements/chunks/{info-C8r5Q6cc.js → info-DX3YMOt6.js} +1 -1
  82. package/dist/elements/chunks/input-BtW9U6Sq.js +1 -0
  83. package/dist/elements/chunks/{kbd-OGJDUT-O.js → kbd-LGXwuT_V.js} +1 -1
  84. package/dist/elements/chunks/{link-BdsfLB7b.js → link-2My69BqT.js} +1 -1
  85. package/dist/elements/chunks/{link-preview-Cf5KOP-i.js → link-preview-OQ9IEq0f.js} +1 -1
  86. package/dist/elements/chunks/{loader-B5QzK_8_.js → loader-iOxc_Tu-.js} +1 -1
  87. package/dist/elements/chunks/{markdown-DbozyYqa.js → markdown-DgBFaGAz.js} +1 -1
  88. package/dist/elements/chunks/{message-DtXKPqiR.js → message-VMZohrXx.js} +1 -1
  89. package/dist/elements/chunks/message-circle-CpXMM-_n.js +1 -0
  90. package/dist/elements/chunks/message-square-DxcWtETQ.js +1 -0
  91. package/dist/elements/chunks/message-tZmV2A3p.js +1 -0
  92. package/dist/elements/chunks/{minimize-2-SegJ_oku.js → minimize-2-DPIUKTrT.js} +1 -1
  93. package/dist/elements/chunks/{model-switcher-D7JrFFHp.js → model-switcher-DsT8tFB0.js} +1 -1
  94. package/dist/elements/chunks/{overlay-CV7z2YuX.js → overlay-DuiwdXjm.js} +1 -1
  95. package/dist/elements/chunks/{paperclip-Bg0ITU3b.js → paperclip-CRZVp4z_.js} +1 -1
  96. package/dist/elements/chunks/{progress-bar-D1_rzt6N.js → progress-bar-DNy66e_E.js} +1 -1
  97. package/dist/elements/chunks/{prompt-suggestion-D5Sm8xF1.js → prompt-suggestion-Bn64Nl7L.js} +1 -1
  98. package/dist/elements/chunks/reasoning-Emn9N4-T.js +1 -0
  99. package/dist/elements/chunks/{resizable-Dpzjlvp4.js → resizable-D181b4fM.js} +1 -1
  100. package/dist/elements/chunks/{rotate-cw-DzVDJOA1.js → rotate-cw-CPWe37D9.js} +1 -1
  101. package/dist/elements/chunks/{scroll-area-93A1bbeG.js → scroll-area-ClZkZxIk.js} +1 -1
  102. package/dist/elements/chunks/scroll-button-DPE-t3x_.js +1 -0
  103. package/dist/elements/chunks/{separator-DH2r_ZSZ.js → separator-CeJHQqLW.js} +1 -1
  104. package/dist/elements/chunks/{settings-BFMOd6yB.js → settings-CqaHgJF9.js} +1 -1
  105. package/dist/elements/chunks/{settings-group-B3VZKIcz.js → settings-group-CjqjLCOn.js} +1 -1
  106. package/dist/elements/chunks/share-CyfnNEqp.js +1 -0
  107. package/dist/elements/chunks/{skeleton-N8BuWYmf.js → skeleton-D8akBnKS.js} +1 -1
  108. package/dist/elements/chunks/slots-DaNjX0rc.js +1 -0
  109. package/dist/elements/chunks/{source-w9V0NnBn.js → source-UWCvmVak.js} +1 -1
  110. package/dist/elements/chunks/{star-DHvACxtm.js → star-DoHFAikQ.js} +1 -1
  111. package/dist/elements/chunks/store-Cjq0qwYN.js +1 -0
  112. package/dist/elements/chunks/{tasks-card-lPxoodlA.js → tasks-card-CmZ5xh6v.js} +1 -1
  113. package/dist/elements/chunks/{text-shimmer-BY_rI3s5.js → text-shimmer-DYn09zrz.js} +1 -1
  114. package/dist/elements/chunks/textarea-skiGHURM.js +1 -0
  115. package/dist/elements/chunks/{thumbs-up-DQe0hvkZ.js → thumbs-up-DX2Ijk3H.js} +1 -1
  116. package/dist/elements/chunks/{toast-store-VUZZBhzH.js → toast-store-CFnQpThX.js} +1 -1
  117. package/dist/elements/chunks/{tool-C1YtxwPu.js → tool-DKQ2D1DP.js} +1 -1
  118. package/dist/elements/chunks/{tooltip-BqwnFWhn.js → tooltip-B0G99y8w.js} +1 -1
  119. package/dist/elements/chunks/{triangle-alert-B6or7F5u.js → triangle-alert-F9cyUEyy.js} +1 -1
  120. package/dist/elements/chunks/{use-card-resolution-DJg32jGH.js → use-card-resolution-DoA2f_EU.js} +1 -1
  121. package/dist/elements/chunks/{variant-aurora-QXTZPyST.js → variant-aurora-qt6Zct8M.js} +2 -2
  122. package/dist/elements/chunks/variant-custom-tJp22_BQ.js +1 -0
  123. package/dist/{variant-wave-BwzrHD1s.js → elements/chunks/variant-wave-CaQ5ti39.js} +2 -2
  124. package/dist/elements/chunks/x-B6UnOiLw.js +1 -0
  125. package/dist/elements/coachmark.js +1 -1
  126. package/dist/elements/code-block.js +1 -1
  127. package/dist/elements/command.js +1 -1
  128. package/dist/elements/compare.js +1 -1
  129. package/dist/elements/composer.js +1 -1
  130. package/dist/elements/confirm-card.js +1 -1
  131. package/dist/elements/context-meter.js +1 -1
  132. package/dist/elements/conversation-item.d.ts +1 -0
  133. package/dist/elements/conversation-item.js +1 -0
  134. package/dist/elements/conversation-list.js +1 -1
  135. package/dist/elements/default-input.d.ts +3 -3
  136. package/dist/elements/define.d.ts +27 -0
  137. package/dist/elements/diagnostic-events.d.ts +74 -0
  138. package/dist/elements/dialog.js +1 -1
  139. package/dist/elements/dock.d.ts +1 -0
  140. package/dist/elements/dock.js +149 -0
  141. package/dist/elements/dropdown.d.ts +1 -0
  142. package/dist/elements/dropdown.js +1 -0
  143. package/dist/elements/editable-label.js +1 -1
  144. package/dist/elements/element-diagnostics.d.ts +81 -0
  145. package/dist/elements/embed.js +1 -1
  146. package/dist/elements/empty.js +1 -1
  147. package/dist/elements/feedback-bar.js +1 -1
  148. package/dist/elements/file-tree.js +1 -1
  149. package/dist/elements/file-upload.js +1 -1
  150. package/dist/elements/form.js +1 -1
  151. package/dist/elements/hover-card.js +1 -1
  152. package/dist/elements/icon.js +1 -1
  153. package/dist/elements/image.js +1 -1
  154. package/dist/elements/input.js +1 -1
  155. package/dist/elements/kbd.js +1 -1
  156. package/dist/elements/link-preview.js +1 -1
  157. package/dist/elements/loader.js +1 -1
  158. package/dist/elements/markdown.js +1 -1
  159. package/dist/elements/menu.js +1 -1
  160. package/dist/elements/message-skills.js +1 -1
  161. package/dist/elements/message.js +1 -1
  162. package/dist/elements/model-switcher.js +1 -1
  163. package/dist/elements/nav.js +1 -1
  164. package/dist/elements/notice.js +1 -1
  165. package/dist/elements/pane-group.js +1 -1
  166. package/dist/elements/pane.js +1 -1
  167. package/dist/elements/popover.js +1 -1
  168. package/dist/elements/progress-bar.js +1 -1
  169. package/dist/elements/prompt-dock.js +1 -1
  170. package/dist/elements/prompt-input.js +1 -1
  171. package/dist/elements/prompt-suggestions.js +1 -1
  172. package/dist/elements/reasoning.js +1 -1
  173. package/dist/elements/register.d.ts +2 -0
  174. package/dist/elements/remote.js +1 -1
  175. package/dist/elements/resizable.js +1 -1
  176. package/dist/elements/response-stream.js +3 -3
  177. package/dist/elements/screen.js +1 -1
  178. package/dist/elements/scroll-area.js +1 -1
  179. package/dist/elements/scroll-button.js +1 -1
  180. package/dist/elements/search.js +1 -1
  181. package/dist/elements/segmented.js +1 -1
  182. package/dist/elements/separator.js +1 -1
  183. package/dist/elements/setting-item.js +1 -1
  184. package/dist/elements/settings-group.js +1 -1
  185. package/dist/elements/skeleton.js +1 -1
  186. package/dist/elements/slots.d.ts +35 -3
  187. package/dist/elements/source.js +1 -1
  188. package/dist/elements/status.js +1 -1
  189. package/dist/elements/switch.js +1 -1
  190. package/dist/elements/tabs.js +1 -1
  191. package/dist/elements/tasks.js +1 -1
  192. package/dist/elements/text-shimmer.js +1 -1
  193. package/dist/elements/thinking-bar.js +1 -1
  194. package/dist/elements/thread.js +1 -1
  195. package/dist/elements/toast.js +1 -1
  196. package/dist/elements/tool.js +1 -1
  197. package/dist/elements/tooltip.js +1 -1
  198. package/dist/elements/voice-input.js +1 -1
  199. package/dist/elements/voice-output.js +1 -1
  200. package/dist/elements.d.ts +232 -148
  201. package/dist/index.d.ts +2 -0
  202. package/dist/index.js +5456 -4931
  203. package/dist/index.server.js +4142 -3669
  204. package/dist/kai-provider.es.js +40 -40
  205. package/dist/kai.es.js +1 -1
  206. package/dist/llms/llms-full.txt +184 -69
  207. package/dist/llms/llms.txt +3 -3
  208. package/dist/mcp.es.js +2669 -546
  209. package/dist/primitives/use-resize-observer.d.ts +23 -0
  210. package/dist/primitives/use-speech-recognition.d.ts +8 -0
  211. package/dist/primitives/use-text-stream.d.ts +5 -0
  212. package/dist/react/index.d.ts +115 -266
  213. package/dist/react.js +247 -221
  214. package/dist/register-impl-DpE7icIb.js +293 -0
  215. package/dist/schemas/index.d.ts +1 -1
  216. package/dist/schemas/tool-defs.d.ts +35 -0
  217. package/dist/schemas.js +403 -310
  218. package/dist/{solid-BMuDmFo_.js → solid-BtdqNZI7.js} +8435 -7394
  219. package/dist/{solid-CGYNdLRR.js → solid-CnavwBye.js} +5987 -5035
  220. package/dist/solid.d.ts +5 -0
  221. package/dist/solid.js +210 -203
  222. package/dist/solid.server.js +210 -203
  223. package/dist/state/index.d.ts +4 -0
  224. package/dist/state/persistence.d.ts +51 -0
  225. package/dist/state/stream.d.ts +50 -0
  226. package/dist/state/threads.d.ts +71 -0
  227. package/dist/state.js +269 -169
  228. package/dist/types.d.ts +4 -2
  229. package/dist/ui/dock.d.ts +109 -0
  230. package/dist/ui/hover-card.d.ts +44 -0
  231. package/dist/{variant-aurora-DwSOfraQ.js → variant-aurora-B8II3-I1.js} +27 -27
  232. package/dist/{variant-aurora-wKvAQ5wp.js → variant-aurora-CMNWOSJR.js} +28 -28
  233. package/dist/{variant-aurora-XEkPT0_z.js → variant-aurora-CcuDMWwQ.js} +2 -2
  234. package/dist/{variant-custom-eFIg2XWQ.js → variant-custom-BdvYC7af.js} +40 -40
  235. package/dist/{variant-custom-BhS9wIpU.js → variant-custom-C714mqKl.js} +65 -65
  236. package/dist/variant-custom-L16JLLZy.js +1 -0
  237. package/dist/{elements/chunks/variant-wave-DDCwhLas.js → variant-wave-2S4La2Ka.js} +2 -2
  238. package/dist/{variant-wave-CbVsmgwT.js → variant-wave-D7ruiA1W.js} +25 -25
  239. package/dist/{variant-wave-CUWGx9Bn.js → variant-wave-ecwykeyL.js} +21 -21
  240. package/dist/wire/chunk.d.ts +47 -0
  241. package/dist/wire/diagnostics.d.ts +575 -0
  242. package/dist/wire/encode-probe.d.ts +70 -0
  243. package/dist/wire/encode.d.ts +24 -0
  244. package/dist/wire/index.d.ts +2 -0
  245. package/dist/wire/sse.d.ts +10 -2
  246. package/dist/wire.js +917 -480
  247. package/frameworks/react/index.tsx +132 -93
  248. package/llms-full.txt +184 -69
  249. package/llms.txt +3 -3
  250. package/package.json +26 -6
  251. package/src/agent-tooling/archetypes.ts +41 -9
  252. package/src/agent-tooling/catalog/README.md +676 -0
  253. package/src/agent-tooling/catalog/catalog-types.ts +231 -0
  254. package/src/agent-tooling/catalog/fabrications.ts +96 -0
  255. package/src/agent-tooling/catalog/invariants.ts +284 -0
  256. package/src/agent-tooling/catalog/labs-titles.ts +114 -0
  257. package/src/agent-tooling/catalog/scenarios.ts +87 -0
  258. package/src/agent-tooling/catalog/surfaces.ts +275 -0
  259. package/src/agent-tooling/integrations/anthropic.ts +7 -1
  260. package/src/agent-tooling/integrations/cloudflare.ts +43 -2
  261. package/src/agent-tooling/integrations/langgraph.ts +7 -1
  262. package/src/agent-tooling/integrations/mastra.ts +7 -1
  263. package/src/agent-tooling/integrations/mock.ts +92 -17
  264. package/src/agent-tooling/integrations/ollama.ts +7 -1
  265. package/src/agent-tooling/integrations/openai.ts +7 -1
  266. package/src/agent-tooling/integrations/openrouter.ts +7 -1
  267. package/src/agent-tooling/integrations/pi.ts +56 -1
  268. package/src/agent-tooling/integrations/vercel-ai-sdk.ts +7 -1
  269. package/src/agent-tooling/mcp/css-raw.d.ts +12 -0
  270. package/src/agent-tooling/mcp/manifest.ts +100 -1
  271. package/src/agent-tooling/mcp/server.ts +61 -5
  272. package/src/agent-tooling/mcp/tools/reference.ts +440 -4
  273. package/src/agent-tooling/mcp/tools/scaffold.ts +1834 -307
  274. package/src/agent-tooling/mcp/tools/theme.ts +146 -30
  275. package/src/agent-tooling/mcp/validate-args.ts +141 -0
  276. package/src/agent-tooling/registry.ts +19 -7
  277. package/src/agent-tooling/route-emit.ts +26 -2
  278. package/src/components/attachment-types.ts +47 -1
  279. package/src/components/attachments.tsx +169 -22
  280. package/src/components/audio-visualizer/fit-scale.ts +112 -0
  281. package/src/components/audio-visualizer/index.tsx +80 -3
  282. package/src/components/audio-visualizer/variant-aurora.tsx +10 -4
  283. package/src/components/audio-visualizer/variant-bar.tsx +38 -6
  284. package/src/components/audio-visualizer/variant-custom.tsx +8 -2
  285. package/src/components/audio-visualizer/variant-grid.tsx +16 -11
  286. package/src/components/audio-visualizer/variant-radial.tsx +15 -10
  287. package/src/components/audio-visualizer/variant-wave.tsx +7 -2
  288. package/src/components/chat-container.tsx +4 -1
  289. package/src/components/chat-thread.tsx +36 -7
  290. package/src/components/code-block.tsx +79 -3
  291. package/src/components/composer.tsx +8 -1
  292. package/src/components/conversation-item.tsx +114 -4
  293. package/src/components/conversation-list.tsx +212 -12
  294. package/src/components/embed.tsx +4 -0
  295. package/src/components/form.tsx +41 -14
  296. package/src/components/message.tsx +75 -5
  297. package/src/components/response-stream.tsx +13 -7
  298. package/src/components/screen.tsx +5 -0
  299. package/src/components/scroll-button.tsx +7 -0
  300. package/src/components/toast.tsx +11 -5
  301. package/src/components/voice-input.tsx +35 -3
  302. package/src/components/voice-output.tsx +46 -6
  303. package/src/components/workspace-shell.tsx +285 -0
  304. package/src/diagnostics/hook.ts +352 -0
  305. package/src/diagnostics/index.ts +82 -0
  306. package/src/diagnostics/report-request.ts +296 -0
  307. package/src/elements/attachments.tsx +6 -7
  308. package/src/elements/audio-visualizer.tsx +48 -4
  309. package/src/elements/chat-workspace.tsx +159 -287
  310. package/src/elements/chat.tsx +12 -9
  311. package/src/elements/code-block.tsx +19 -1
  312. package/src/elements/command.tsx +4 -1
  313. package/src/elements/compiled.css +1 -1
  314. package/src/elements/conversation-item.tsx +80 -0
  315. package/src/elements/conversation-list.tsx +60 -11
  316. package/src/elements/default-input.tsx +5 -5
  317. package/src/elements/define.tsx +168 -8
  318. package/src/elements/diagnostic-events.ts +114 -0
  319. package/src/elements/dialog.tsx +47 -0
  320. package/src/elements/disclosure.ts +34 -12
  321. package/src/elements/dock.tsx +204 -0
  322. package/src/elements/dropdown.tsx +123 -0
  323. package/src/elements/element-diagnostics.ts +392 -0
  324. package/src/elements/element-manifest.json +12 -0
  325. package/src/elements/element-meta.json +672 -439
  326. package/src/elements/element-nonscalar.json +170 -0
  327. package/src/elements/element-types.d.ts +232 -148
  328. package/src/elements/menu.tsx +13 -0
  329. package/src/elements/prompt-dock.tsx +2 -1
  330. package/src/elements/prompt-input.tsx +8 -8
  331. package/src/elements/register-impl.ts +46 -0
  332. package/src/elements/register.ts +28 -0
  333. package/src/elements/resizable.tsx +52 -25
  334. package/src/elements/scroll-button.tsx +5 -0
  335. package/src/elements/slots.ts +107 -12
  336. package/src/elements/styles.css +30 -6
  337. package/src/elements/thinking-bar.tsx +16 -7
  338. package/src/elements/toast.tsx +3 -0
  339. package/src/elements/tool.tsx +26 -5
  340. package/src/elements/voice-input.tsx +11 -2
  341. package/src/elements/voice-output.tsx +16 -3
  342. package/src/index.ts +2 -0
  343. package/src/primitives/use-resize-observer.ts +33 -0
  344. package/src/primitives/use-speech-recognition.ts +12 -1
  345. package/src/primitives/use-stick-to-bottom.ts +34 -2
  346. package/src/primitives/use-text-stream.ts +33 -2
  347. package/src/remote/provider-runtime.ts +6 -3
  348. package/src/schemas/index.ts +1 -0
  349. package/src/schemas/tool-defs.ts +297 -6
  350. package/src/solid.ts +5 -0
  351. package/src/state/index.ts +12 -0
  352. package/src/state/persistence.ts +180 -0
  353. package/src/state/stream.ts +71 -4
  354. package/src/state/threads.ts +142 -0
  355. package/src/types.ts +4 -2
  356. package/src/ui/dialog.tsx +6 -0
  357. package/src/ui/dock.tsx +445 -0
  358. package/src/ui/dropdown.tsx +67 -23
  359. package/src/ui/hover-card.tsx +156 -6
  360. package/src/ui/input.tsx +29 -10
  361. package/src/ui/resizable.tsx +11 -0
  362. package/src/wire/chunk.ts +47 -0
  363. package/src/wire/consume.ts +185 -26
  364. package/src/wire/diagnostics.ts +727 -0
  365. package/src/wire/encode-probe.ts +214 -0
  366. package/src/wire/encode.ts +267 -14
  367. package/src/wire/formats/anthropic.ts +8 -1
  368. package/src/wire/formats/openai.ts +5 -0
  369. package/src/wire/index.ts +22 -0
  370. package/src/wire/read.ts +250 -6
  371. package/src/wire/sse.ts +16 -3
  372. package/dist/elements/chunks/action-icons-DW9muWrY.js +0 -1
  373. package/dist/elements/chunks/arrow-left-iJVnQmxA.js +0 -1
  374. package/dist/elements/chunks/attachments-CL6TaqIb.js +0 -1
  375. package/dist/elements/chunks/audio-visualizer-BuUqQmaO.js +0 -1
  376. package/dist/elements/chunks/chat-thread-DxoKaStk.js +0 -1
  377. package/dist/elements/chunks/check-MdLzbxrm.js +0 -1
  378. package/dist/elements/chunks/chevron-down-MK7IzDqe.js +0 -1
  379. package/dist/elements/chunks/chevron-right-C5jXhmtV.js +0 -1
  380. package/dist/elements/chunks/circle-BG7VkPep.js +0 -1
  381. package/dist/elements/chunks/circle-check-GqvO2UBs.js +0 -1
  382. package/dist/elements/chunks/code-block-ClhQO9RW.js +0 -1
  383. package/dist/elements/chunks/collapsible-DBcBkFPh.js +0 -1
  384. package/dist/elements/chunks/context-hKjNqiyQ.js +0 -1
  385. package/dist/elements/chunks/conversation-list-CsGJjSKG.js +0 -1
  386. package/dist/elements/chunks/default-input-B1Mem5wu.js +0 -1
  387. package/dist/elements/chunks/define-B_tXMYRm.js +0 -1
  388. package/dist/elements/chunks/disclosure-DAoYOKew.js +0 -1
  389. package/dist/elements/chunks/dropdown-CmNna5lT.js +0 -1
  390. package/dist/elements/chunks/embed-CjAD2jSi.js +0 -1
  391. package/dist/elements/chunks/form-Cp8Y00Wn.js +0 -1
  392. package/dist/elements/chunks/hover-card-6KtXpLwT.js +0 -1
  393. package/dist/elements/chunks/icon-97R8SR2p.js +0 -1
  394. package/dist/elements/chunks/input-DrseEjhp.js +0 -1
  395. package/dist/elements/chunks/message-CE4k-qaj.js +0 -1
  396. package/dist/elements/chunks/message-square-C9ovmtOo.js +0 -1
  397. package/dist/elements/chunks/reasoning-BrxQfpfI.js +0 -1
  398. package/dist/elements/chunks/scroll-button-CrTo7wm4.js +0 -1
  399. package/dist/elements/chunks/share-DgNbkJKY.js +0 -1
  400. package/dist/elements/chunks/slots-CIw9RlAe.js +0 -1
  401. package/dist/elements/chunks/store-HIPGhNz9.js +0 -1
  402. package/dist/elements/chunks/textarea-DPfuhswM.js +0 -1
  403. package/dist/elements/chunks/variant-custom-D5ZlmziF.js +0 -1
  404. package/dist/elements/chunks/video-Bb86fVYI.js +0 -1
  405. package/dist/elements/chunks/x-BOhjBeOd.js +0 -1
  406. package/dist/primitives/card-validate-generator.testlib.d.ts +0 -20
  407. package/dist/register-impl-DGmGzrEM.js +0 -145
  408. package/dist/variant-custom-MkTNTycd.js +0 -1
  409. package/src/primitives/card-validate-generator.testlib.ts +0 -38
@@ -35,9 +35,22 @@ import type { Integration } from '../types';
35
35
  * Swap `integration` for a real provider (openai, anthropic, openrouter, ollama,
36
36
  * …) when ready.
37
37
  *
38
- * It carries an empty `routeTemplates` on purpose: there is no server route to
39
- * emit. `category: 'mock'` keeps it out of the real provider/gateway/framework/
40
- * harness catalogs.
38
+ * THE ROUTE IS OPTIONAL, AND IT EXISTS ANYWAY (rung-2 finding G-04, recurring
39
+ * since rung 1). Two clean-room builders in a row were told "replies come from a
40
+ * local dev endpoint that streams a mocked response" and had to hand-invent the
41
+ * server side, because this was the one integration whose block (2) held prose
42
+ * where every other integration ships code. So `webRoute` below is the mock's
43
+ * server half: the SAME `createMockResponder()` frames, written to the wire by a
44
+ * portable `chatHandler(Request) => Response` that the scaffolder wraps with the
45
+ * identical per-framework adapters a real integration gets (Vite dev middleware
46
+ * for the SPA frameworks, POST exports for next/svelte/tanstack, the
47
+ * Express/Worker/Angular hosts). Nothing about the emitted app REQUIRES it — the
48
+ * front end streams locally and never fetches — which the emitted block states,
49
+ * along with the one-expression swap that points onSubmit at it.
50
+ *
51
+ * `routeTemplates` stays empty: the handler is web-standard, so there is no
52
+ * framework whose declaration it cannot be wrapped in. `category: 'mock'` keeps
53
+ * it out of the real provider/gateway/framework/harness catalogs.
41
54
  */
42
55
  const mock: Integration = {
43
56
  id: 'mock',
@@ -47,26 +60,88 @@ const mock: Integration = {
47
60
  streamFormat: 'native',
48
61
  envVars: [],
49
62
  routeTemplates: {},
63
+ webRoute: `import { createMockResponder } from '@kitn.ai/ui/state';
64
+
65
+ // The kit's own mock responder — no provider, no key, no upstream. MODULE
66
+ // scope, not per-request: the responder owns the cursor into its canned
67
+ // replies, so rebuilding it per turn would answer with the first one forever.
68
+ const mockResponse = createMockResponder();
69
+
70
+ async function chatHandler(request: Request): Promise<Response> {
71
+ // The responder cycles its replies whatever you send, so the prompt is a
72
+ // courtesy — but the body is still read exactly the way a real route reads
73
+ // it, so swapping this handler for a provider's changes nothing upstream of
74
+ // this file.
75
+ let chatBody: ChatRequestBody;
76
+ try {
77
+ chatBody = await readChatRequest(request);
78
+ } catch (error) {
79
+ return toChatErrorResponse(error);
80
+ }
81
+ const { messages } = chatBody;
82
+ const last = [...messages].reverse().find((m) => m.role === 'user');
83
+ const prompt = last && typeof last.content === 'string' ? last.content : '';
84
+
85
+ // createMockResponder() yields COMPLETE OpenAI chat-completions SSE frames —
86
+ // the same strings the front end's local preview streams — so this route only
87
+ // writes them out verbatim. No framing is built here; the browser parses them
88
+ // with readOpenAIStream from @kitn.ai/ui/wire, exactly as it would a real
89
+ // route's response. Every frame is marked as a mock (a ': kai-mock' banner, a
90
+ // _kai_mock field, model 'kai-mock', zero usage).
91
+ const encoder = new TextEncoder();
92
+ let open = true;
93
+ const body = new ReadableStream<Uint8Array>({
94
+ async start(controller) {
95
+ for await (const frame of mockResponse(prompt)) {
96
+ // The browser hangs up when the user asks a new question mid-answer;
97
+ // stop producing frames rather than writing into a cancelled stream.
98
+ if (!open) return;
99
+ controller.enqueue(encoder.encode(frame));
100
+ }
101
+ controller.close();
102
+ },
103
+ cancel() {
104
+ open = false;
105
+ },
106
+ });
107
+
108
+ return new Response(body, {
109
+ status: 200,
110
+ headers: {
111
+ 'Content-Type': 'text/event-stream; charset=utf-8',
112
+ // no-transform stops a proxy buffering the stream into one blob.
113
+ 'Cache-Control': 'no-cache, no-transform',
114
+ Connection: 'keep-alive',
115
+ // A proxy in front of a preview build would otherwise buffer the whole
116
+ // stream and the reply would land all at once instead of streaming.
117
+ 'X-Accel-Buffering': 'no',
118
+ },
119
+ });
120
+ }`,
50
121
  streamMapping:
51
- "No backend and no provider, but a real wire. createMockResponder() from @kitn.ai/ui/state yields canned SSE frames and the scaffold reads them with readOpenAIStream from @kitn.ai/ui/wire, on the same code path a real route's response takes: same reader, same part folding, same abort handling. The frames carry the OpenAI chat-completions shape because the mock stands in for your /api/chat ROUTE, not for a provider (every other integration here re-frames its provider to that shape server-side), so going live replaces ONE expression: mockResponse(value) becomes the awaited response from your own chat route, and nothing else in the handler changes. Nothing here can be mistaken for a real turn: the stream opens with a ': kai-mock' SSE comment, every frame carries a _kai_mock field naming createMockResponder, model reports as 'kai-mock' (no provider serves it), and usage is all zeros.",
122
+ "No backend and no provider, but a real wire. createMockResponder() from @kitn.ai/ui/state yields canned SSE frames and the scaffold reads them with readOpenAIStream from @kitn.ai/ui/wire, on the same code path a real route's response takes: same reader, same part folding, same abort handling. The frames carry the OpenAI chat-completions shape because the mock stands in for your /api/chat ROUTE, not for a provider (every other integration here re-frames its provider to that shape server-side), so going live replaces ONE expression: mockResponse(value) becomes the awaited response from your own chat route, and nothing else in the handler changes. Nothing here can be mistaken for a real turn: the stream opens with a ': kai-mock' SSE comment, every frame carries a _kai_mock field naming createMockResponder, model reports as 'kai-mock' (no provider serves it), and usage is all zeros. Block (2) serves the same frames over HTTP as an OPTIONAL route, so the /api/chat seam can be stood up before any provider exists.",
52
123
  runNote:
53
- 'No backend or API key needed: the reply is generated in the browser and parsed by the same reader a real route feeds. Run the front-end as-is; swap `integration` for a real provider (openai, anthropic, openrouter, ollama) when ready, and the emitted handler differs by one expression.',
124
+ 'No backend or API key needed: the reply is generated in the browser and parsed by the same reader a real route feeds. Run the front-end as-is; swap `integration` for a real provider (openai, anthropic, openrouter, ollama) when ready, and the emitted handler differs by one expression. Block (2) is the OPTIONAL server half: the same mock frames served over HTTP, for standing up the /api/chat seam before any provider exists.',
54
125
  docsSlug: 'integrations/mock',
55
- // Nothing: there is no HTTP request at all. The frames are produced in-process
56
- // by createMockResponder(), so there is no body to forward a model or a tools
57
- // array on, and no route that would read one.
126
+ // Nothing: the front end makes no HTTP request at all, and the optional route
127
+ // reads only `messages` and only as a courtesy prompt the responder is free
128
+ // to ignore. There is no model to pick and no tools array anything would run.
58
129
  forwardsFromClient: [],
59
- // Nothing to install: there is no route to install anything for.
130
+ // Nothing to install: the route's one import is the kit itself, which every
131
+ // scaffold already depends on (registry.test.ts pins deps.npm to the route's
132
+ // imports minus @kitn.ai/ui).
60
133
  deps: { npm: [], pip: [] },
61
- // The other 'frontend-safe' entry, and the only one that is true by absence:
62
- // no routeTemplates, no webRoute, no envVars. There is no request, no upstream
63
- // and no secret, so there is nothing a public bundle could give away. This is
64
- // the one place where "declares nothing" genuinely means safe — which is
65
- // precisely why it still has to SAY so rather than be left blank.
134
+ // The other 'frontend-safe' entry. The route now exists (G-04) but changes
135
+ // nothing here: it holds no credential, sends no auth header and reaches no
136
+ // upstream, so there is still nothing a public bundle could give away. The
137
+ // schema's own refinement re-checks that claim against the route source.
66
138
  keyExposure: 'frontend-safe',
67
- // Nothing at all, and here that is the literal truth rather than a shorthand:
68
- // there is no route, no upstream and no process. This is the "No backend"
69
- // group of create-kai's gateway prompt all by itself.
139
+ // Nothing to supply out of band: no upstream, no process, no runtime. The
140
+ // route is optional the emitted app streams locally without it which is
141
+ // what keeps mock in the "No backend" group of create-kai's gateway prompt
142
+ // (listGatewayGroups derives that from frontend-safe + outOfBand 'none', not
143
+ // from the absence of a route, precisely because this route is one nobody
144
+ // NEEDS).
70
145
  outOfBand: 'none',
71
146
  };
72
147
 
@@ -20,7 +20,13 @@ const ollama: Integration = {
20
20
  // The model is pinned here, not sent by the browser. tools IS forwarded:
21
21
  // it is undefined unless the front end declared any, and JSON.stringify
22
22
  // drops it, so the same handler serves a tool archetype and a plain chat.
23
- const { messages, tools } = await readChatRequest(request);
23
+ let chatBody: ChatRequestBody;
24
+ try {
25
+ chatBody = await readChatRequest(request);
26
+ } catch (error) {
27
+ return toChatErrorResponse(error);
28
+ }
29
+ const { messages, tools } = chatBody;
24
30
 
25
31
  const upstream = await fetch('http://localhost:11434/v1/chat/completions', {
26
32
  method: 'POST',
@@ -14,7 +14,13 @@ const openai: Integration = {
14
14
  // Both model and tools come from the browser. \`tools\` is undefined unless the
15
15
  // front end declared any, and JSON.stringify drops it, so the same handler
16
16
  // serves a tool archetype and a plain chat.
17
- const { model, messages, tools } = await readChatRequest(request);
17
+ let chatBody: ChatRequestBody;
18
+ try {
19
+ chatBody = await readChatRequest(request);
20
+ } catch (error) {
21
+ return toChatErrorResponse(error);
22
+ }
23
+ const { model, messages, tools } = chatBody;
18
24
 
19
25
  const upstream = await fetch('https://api.openai.com/v1/chat/completions', {
20
26
  method: 'POST',
@@ -14,7 +14,13 @@ const openrouter: Integration = {
14
14
  webRoute: `async function chatHandler(request: Request): Promise<Response> {
15
15
  // tools is undefined unless the front end sent one; JSON.stringify drops it,
16
16
  // so the same handler serves a tool archetype and a plain chat.
17
- const { model, messages, tools } = await readChatRequest(request);
17
+ let chatBody: ChatRequestBody;
18
+ try {
19
+ chatBody = await readChatRequest(request);
20
+ } catch (error) {
21
+ return toChatErrorResponse(error);
22
+ }
23
+ const { model, messages, tools } = chatBody;
18
24
 
19
25
  const upstream = await fetch('https://openrouter.ai/api/v1/chat/completions', {
20
26
  method: 'POST',
@@ -20,9 +20,64 @@ import type { OpenAIWireMessage } from '@kitn.ai/ui/wire';
20
20
  const app = express();
21
21
  app.use(express.json());
22
22
 
23
+ class ChatRequestError extends Error {
24
+ constructor(readonly status: number, message: string) { super(message); }
25
+ }
26
+
27
+ /** Narrow the JSON body once, at the edge. A bare GET or a missing messages
28
+ * array is a ChatRequestError with a status — NEVER an unhandled throw
29
+ * (findings F-10). Malformed JSON is rejected upstream by express.json(),
30
+ * which the error handler below turns into a JSON 400. */
31
+ function readChatRequest(req: express.Request): { messages: OpenAIWireMessage[] } {
32
+ if (req.method !== 'POST') {
33
+ throw new ChatRequestError(405, \`Method \${req.method} not allowed — POST /api/chat.\`);
34
+ }
35
+ const body = (req.body ?? {}) as { messages?: OpenAIWireMessage[] };
36
+ if (!Array.isArray(body.messages)) {
37
+ throw new ChatRequestError(400, 'Request body must carry a messages array.');
38
+ }
39
+ return body as { messages: OpenAIWireMessage[] };
40
+ }
41
+
42
+ /** Map a guard rejection to the response its status demands; rethrow anything
43
+ * else — an unexpected error should be loud, not laundered into a 400. */
44
+ function toChatErrorResponse(error: unknown, res: express.Response): void {
45
+ if (error instanceof ChatRequestError) {
46
+ res.status(error.status).json({ error: { message: error.message } });
47
+ return;
48
+ }
49
+ throw error;
50
+ }
51
+
52
+ // express.json() rejects a malformed body at the MIDDLEWARE level, before any
53
+ // handler runs — without this it surfaces as Express's default HTML error page.
54
+ app.use(
55
+ (
56
+ err: { status?: number; message?: string },
57
+ _req: express.Request,
58
+ res: express.Response,
59
+ _next: express.NextFunction,
60
+ ) => {
61
+ res.status(err?.status ?? 500).json({ error: { message: err?.message ?? 'Request failed.' } });
62
+ },
63
+ );
64
+
65
+ // A bare GET is a 405 with the method named, not Express's default 404 —
66
+ // the resource exists, the verb is wrong.
67
+ app.get('/api/chat', (_req, res) => {
68
+ res.status(405).json({ error: { message: 'Method not allowed — POST /api/chat.' } });
69
+ });
70
+
23
71
  // POST /api/chat: bridge a Pi RPC session to the browser as SSE.
24
72
  app.post('/api/chat', (req, res) => {
25
- const { messages } = req.body as { messages: OpenAIWireMessage[] };
73
+ let chatBody: { messages: OpenAIWireMessage[] };
74
+ try {
75
+ chatBody = readChatRequest(req);
76
+ } catch (error) {
77
+ toChatErrorResponse(error, res);
78
+ return;
79
+ }
80
+ const { messages } = chatBody;
26
81
  const last = messages.at(-1)?.content;
27
82
  // \`content\` is a plain string until the turn carries an attachment, at which
28
83
  // point it is an ARRAY of content parts. Pi's RPC mode takes a TEXT prompt and
@@ -169,7 +169,13 @@ const FINISH_REASONS: Record<string, string> = {
169
169
  };
170
170
 
171
171
  async function chatHandler(request: Request): Promise<Response> {
172
- const { messages, tools } = await readChatRequest(request);
172
+ let chatBody: ChatRequestBody;
173
+ try {
174
+ chatBody = await readChatRequest(request);
175
+ } catch (error) {
176
+ return toChatErrorResponse(error);
177
+ }
178
+ const { messages, tools } = chatBody;
173
179
  const toolSet = toToolSet(tools);
174
180
  const prompt = toModelMessages(messages);
175
181
 
@@ -0,0 +1,12 @@
1
+ // `*.css?raw` — Vite inlines the file's TEXT as a string at build time.
2
+ //
3
+ // tools/theme.ts uses it to read the real `--kai-*` token names out of
4
+ // theme.css instead of restating them. The declaration lives here rather than
5
+ // coming from `vite/client` because tsconfig.mcp.json deliberately runs with
6
+ // `types: ["node"]` and no DOM — this is a Node bundle that emits HTML as
7
+ // strings. One narrow module pattern is cheaper than pulling the whole browser
8
+ // client typing into that pass.
9
+ declare module '*.css?raw' {
10
+ const content: string;
11
+ export default content;
12
+ }
@@ -17,7 +17,7 @@
17
17
  * `cardTagForType` for the rule and for what happens when an eighth card type lands.
18
18
  */
19
19
 
20
- import { readFileSync, existsSync } from 'node:fs';
20
+ import { readFileSync, readdirSync, existsSync } from 'node:fs';
21
21
  import { fileURLToPath } from 'node:url';
22
22
  import { join, dirname, resolve } from 'node:path';
23
23
  // The package's own public entry, by the same specifier the scaffolder tells a
@@ -236,6 +236,105 @@ export function listElements(): string[] {
236
236
  .sort();
237
237
  }
238
238
 
239
+ // ── Per-element entry map ─────────────────────────────────────────────────────
240
+ //
241
+ // A STATIC import, not an fs read off resolveManifestPath's dual-context pattern:
242
+ // element-manifest.json (unlike custom-elements.json) is never copied into dist/,
243
+ // so an fs read relative to this module's own URL would resolve in the vitest/source
244
+ // context and 404 in the bundled dist/mcp.es.js. A static import sidesteps the gap
245
+ // entirely — Rollup inlines the JSON at build time, so the bundled bin carries the
246
+ // data with no runtime file dependency. NAMED import (not the whole module), matching
247
+ // the same tradeoff element-diagnostics.ts already made for this file: a default
248
+ // import would pull `files` in too for 0 benefit here.
249
+ import { tags as ELEMENT_ENTRY_TAGS } from '../../elements/element-manifest.json';
250
+
251
+ /**
252
+ * The per-element entry basename for a tag, e.g. 'kai-chat' -> 'chat'.
253
+ *
254
+ * Read from element-manifest.json's `tags` map rather than derived by stripping
255
+ * the `kai-` prefix: TEN of the eighty elements do not match that derivation
256
+ * (`kai-conversations` -> `conversation-list`), so a derived path would emit a
257
+ * broken import for them.
258
+ */
259
+ export function entryForTag(tag: string): string | undefined {
260
+ return (ELEMENT_ENTRY_TAGS as Record<string, string>)[tag];
261
+ }
262
+
263
+ /**
264
+ * The per-element entry basename for a tag the register-all bundle does NOT
265
+ * carry, e.g. 'kai-remote' -> 'remote'. `undefined` for every tag `entryForTag`
266
+ * already answers, and for a tag no built module registers.
267
+ *
268
+ * WHY THIS EXISTS. `entryForTag` reads element-manifest.json, which is generated
269
+ * from the import list in register-impl.ts — so an opt-in element is absent from
270
+ * it by construction, and the reference had nothing to name. It said "find the
271
+ * entry point in the package's published exports", which is the one place in the
272
+ * reference where "how to make this element exist" does not answer itself.
273
+ *
274
+ * WHY IT IS READ AND NOT DERIVED. Stripping `kai-` is wrong for ten of the eighty
275
+ * elements, so guessing here risks exactly the broken import this tool exists to
276
+ * catch. The fact is stated in vite.config.elements.ts, which adds an explicit
277
+ * entry for each opt-in element precisely "so `@kitn.ai/ui/elements/remote`
278
+ * resolves to a real dist file" — but a vite config is not shipped in the
279
+ * package, so the runtime reads that intent where it LANDS: dist/elements/. A
280
+ * candidate is a built module that element-manifest.json does not already claim
281
+ * (plus the two non-element entries the same build emits), and it is matched to a
282
+ * tag by the tag literal it registers. So the answer is the built artifact's,
283
+ * confirmed against the tag rather than assumed from a filename.
284
+ *
285
+ * dist/elements/ is a sibling of the manifest in both contexts — the bundled bin
286
+ * (dist/mcp.es.js) and vitest-over-source, which resolveManifestPath already
287
+ * normalises — so this needs no second resolution strategy. A missing or
288
+ * unreadable directory yields `undefined` rather than throwing: the caller's
289
+ * fallback is the honest "this is opt-in" paragraph it already prints.
290
+ */
291
+ export function optInEntryForTag(tag: string): string | undefined {
292
+ if (entryForTag(tag)) return undefined;
293
+ return optInEntries().get(tag);
294
+ }
295
+
296
+ /** Not element entries: the register-all barrel and the DOM autoloader, both
297
+ * emitted into dist/elements/ by the same per-element build. */
298
+ const NON_ELEMENT_ENTRIES = new Set(['index', 'autoloader']);
299
+
300
+ let _optInEntries: Map<string, string> | undefined;
301
+
302
+ function optInEntries(): Map<string, string> {
303
+ if (_optInEntries) return _optInEntries;
304
+ _optInEntries = new Map();
305
+
306
+ const dir = join(dirname(resolveManifestPath()), 'elements');
307
+ let candidates: string[];
308
+ try {
309
+ const registered = new Set(Object.values(ELEMENT_ENTRY_TAGS as Record<string, string>));
310
+ candidates = readdirSync(dir)
311
+ .filter((f) => f.endsWith('.js'))
312
+ .map((f) => f.slice(0, -'.js'.length))
313
+ .filter((base) => !registered.has(base) && !NON_ELEMENT_ENTRIES.has(base));
314
+ } catch {
315
+ return _optInEntries;
316
+ }
317
+
318
+ // Only the tags element-manifest.json does not already answer for. Narrow on
319
+ // purpose: an element module may mention a tag it merely renders, and matching
320
+ // against the whole tag list would let that be read as a registration.
321
+ const unclaimed = listElements().filter((t) => !entryForTag(t));
322
+
323
+ for (const base of candidates) {
324
+ let code: string;
325
+ try {
326
+ code = readFileSync(join(dir, `${base}.js`), 'utf-8');
327
+ } catch {
328
+ continue;
329
+ }
330
+ for (const tag of unclaimed) {
331
+ if (!_optInEntries.has(tag) && code.includes(`"${tag}"`)) _optInEntries.set(tag, base);
332
+ }
333
+ }
334
+
335
+ return _optInEntries;
336
+ }
337
+
239
338
  // ── Which elements have anything to do with cards ────────────────────────────
240
339
  //
241
340
  // Two different populations, and conflating them would be the "attaches card
@@ -3,15 +3,65 @@ import {
3
3
  CallToolRequestSchema,
4
4
  ListToolsRequestSchema,
5
5
  } from '@modelcontextprotocol/sdk/types.js';
6
+ import { createRequire } from 'node:module';
6
7
  import { z } from 'zod';
7
8
  import { reference } from './tools/reference';
8
9
  import { scaffold } from './tools/scaffold';
9
10
  import { theme } from './tools/theme';
10
11
  import { debug } from './tools/debug';
11
12
  import type { Tool } from './tools/types';
13
+ import { validateToolArgs } from './validate-args';
12
14
 
13
15
  const tools: Tool[] = [reference, scaffold, theme, debug];
14
16
 
17
+ const PACKAGE_NAME = '@kitn.ai/ui';
18
+
19
+ /**
20
+ * The `serverInfo` every MCP harness reads back on `initialize`. READ, NOT TYPED.
21
+ *
22
+ * This was a hand-typed `{ name: '@kitn.ai/ui', version: '0.15.0' }` and the kit kept
23
+ * releasing past it, so every harness that connected was told a version ten minors
24
+ * stale — the same "derive it, don't type it" failure as the CDN pins, minus their
25
+ * lint. Nothing here is allowed to restate the version: `server.test.ts` reads
26
+ * package.json off disk itself and compares.
27
+ *
28
+ * RESOLVED BY THE PACKAGE'S OWN NAME, not by path arithmetic. This module runs from
29
+ * two places — `<package>/src/agent-tooling/mcp/server.ts` under vitest, and inlined
30
+ * into `<package>/dist/mcp.es.js` for the bin — which sit at different depths, so a
31
+ * relative hop would need one rule per context (what manifest.ts has to do, because
32
+ * `dist/custom-elements.json` is a build artifact with no name to ask for). A
33
+ * self-reference has no depth to get wrong: Node resolves `@kitn.ai/ui/package.json`
34
+ * against the nearest package.json whose `name` matches, which is this package in
35
+ * both contexts and in an install. It also never escapes into a neighbouring
36
+ * checkout the way a parent-directory search can.
37
+ *
38
+ * WHY IT SURVIVES PUBLISHING. npm always ships package.json, `exports` carries
39
+ * `"./package.json": "./package.json"` (self-reference is restricted to the exports
40
+ * map, so that key is load-bearing — drop it and this throws, and the test goes red
41
+ * with it), and `bin/mcp.js` imports `../dist/mcp.es.js` by its own URL, so the
42
+ * resolver starts inside the installed package however it was launched (npx, global
43
+ * install, symlink). Not a JSON import: that inlines the version at BUILD time, which
44
+ * is a second copy that can disagree with the package.json shipped beside it, and it
45
+ * would need `resolveJsonModule` on the repo's browser tsc pass as well as this one.
46
+ *
47
+ * A broken resolve throws instead of falling back to a placeholder — `bin/mcp.js`
48
+ * turns that into a named fatal on stderr, which is the loud version. A wrong version
49
+ * silently announced to every harness is the quiet one.
50
+ */
51
+ function packageIdentity(): { name: string; version: string } {
52
+ const specifier = `${PACKAGE_NAME}/package.json`;
53
+ const require = createRequire(import.meta.url);
54
+ const pkg = require(specifier) as { name?: string; version?: string };
55
+ if (pkg.name !== PACKAGE_NAME || typeof pkg.version !== 'string') {
56
+ throw new Error(
57
+ `[${PACKAGE_NAME}] Refusing to report a serverInfo that is not this package's: ` +
58
+ `${specifier} resolved to ${require.resolve(specifier)}, whose name is ` +
59
+ `${JSON.stringify(pkg.name)} and version ${JSON.stringify(pkg.version)}.`,
60
+ );
61
+ }
62
+ return { name: pkg.name, version: pkg.version };
63
+ }
64
+
15
65
  /**
16
66
  * The AI/UI MCP server. A stdio server (see ./stdio.ts) exposing the AI/UI tools
17
67
  * to any MCP harness. Registers four tools; Tasks 2–5 fill in each handler.
@@ -24,10 +74,7 @@ export interface AiUiServer extends Server {
24
74
  }
25
75
 
26
76
  export function createServer(): AiUiServer {
27
- const server = new Server(
28
- { name: '@kitn.ai/ui', version: '0.15.0' },
29
- { capabilities: { tools: {} } },
30
- ) as AiUiServer;
77
+ const server = new Server(packageIdentity(), { capabilities: { tools: {} } }) as AiUiServer;
31
78
 
32
79
  const byName = new Map(tools.map((t) => [t.name, t]));
33
80
 
@@ -50,7 +97,16 @@ export function createServer(): AiUiServer {
50
97
  content: [{ type: 'text', text: `Unknown tool: ${request.params.name}` }],
51
98
  };
52
99
  }
53
- return tool.handler(request.params.arguments ?? {});
100
+ const args = request.params.arguments ?? {};
101
+ // Enforce the schema each tool ADVERTISES (additionalProperties: false +
102
+ // required) — one path for all four tools. Without this, a wrong key was
103
+ // silently ignored and the handler answered a different question (see
104
+ // validate-args.ts for the observed failure).
105
+ const problem = validateToolArgs(tool.name, tool.inputSchema, args);
106
+ if (problem) {
107
+ return { isError: true, content: [{ type: 'text', text: problem }] };
108
+ }
109
+ return tool.handler(args);
54
110
  });
55
111
 
56
112
  server.__listToolsForTest = () => tools.map((t) => t.name);