openclaw-multi-auto 1.3.6 → 1.3.8

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 (328) hide show
  1. package/dist/{audio-preflight-5FEeDooz.js → audio-preflight-DDBLZBdb.js} +4 -4
  2. package/dist/{audio-transcription-runner-B-UvoDjZ.js → audio-transcription-runner-DZbSWT9E.js} +1 -1
  3. package/dist/build-info.json +3 -3
  4. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  5. package/dist/{chrome-D45SyhQL.js → chrome-CMU2WVFh.js} +8 -8
  6. package/dist/{deliver-B9cys0EZ.js → deliver-BXVcFIHL.js} +1 -1
  7. package/dist/{deliver-runtime-DhaQJ0pI.js → deliver-runtime-DTaIS-1i.js} +3 -3
  8. package/dist/{deps-send-whatsapp.runtime-DvTL2tzN.js → deps-send-whatsapp.runtime-CIZqFAqb.js} +7 -7
  9. package/dist/extensionAPI.js +6 -6
  10. package/dist/{image-DAOPwVXi.js → image-BCVLo0qw.js} +1 -1
  11. package/dist/{image-runtime-wlCLVvVv.js → image-runtime-DtCKpMPZ.js} +3 -3
  12. package/dist/{pi-embedded-DYU79yGe.js → pi-embedded-CgQ_W6Xs.js} +24 -24
  13. package/dist/{pi-embedded-helpers-uTRAmQ4n.js → pi-embedded-helpers-CwuBTKza.js} +3 -3
  14. package/dist/plugin-sdk/{accounts-DyFCXtHv.js → accounts-BslAlVYS.js} +2 -2
  15. package/dist/plugin-sdk/{accounts-BJAXxY46.js → accounts-C3m65--E.js} +2 -2
  16. package/dist/plugin-sdk/{accounts-C1j7HSL0.js → accounts-CNCCkdEF.js} +3 -3
  17. package/dist/plugin-sdk/{active-listener-CftX5jLD.js → active-listener-CkPnMUkB.js} +2 -2
  18. package/dist/plugin-sdk/{api-key-rotation-8nyyt1kx.js → api-key-rotation-BXnNsojA.js} +2 -2
  19. package/dist/plugin-sdk/{audio-preflight-C_aSAPR1.js → audio-preflight-CtO4fFvp.js} +26 -26
  20. package/dist/plugin-sdk/{audio-transcription-runner-CB53F7_7.js → audio-transcription-runner-DnxvOS1-.js} +11 -11
  21. package/dist/plugin-sdk/{audit-membership-runtime-BXndI4LG.js → audit-membership-runtime-BpfoSk8M.js} +2 -2
  22. package/dist/plugin-sdk/{channel-activity-C5y8AgAV.js → channel-activity-WJYxcJ3S.js} +3 -3
  23. package/dist/plugin-sdk/{channel-web-DBTRO03V.js → channel-web-dO5k3ubM.js} +18 -18
  24. package/dist/plugin-sdk/{chrome-f00sZkDX.js → chrome-CjNTuJML.js} +6 -6
  25. package/dist/plugin-sdk/{commands-registry-BJ_NxG2F.js → commands-registry-CdYjoI0i.js} +4 -4
  26. package/dist/plugin-sdk/{common-Cf27Jwxu.js → common-oYc5vPFl.js} +2 -2
  27. package/dist/plugin-sdk/{config-CHQrpx-Q.js → config-B1z-UxQ3.js} +7 -7
  28. package/dist/plugin-sdk/{deliver-DNEuetST.js → deliver-D5_6T567.js} +10 -10
  29. package/dist/plugin-sdk/deliver-runtime-C5dgvvga.js +32 -0
  30. package/dist/plugin-sdk/deps-send-discord.runtime-Dg4N7PHJ.js +23 -0
  31. package/dist/plugin-sdk/deps-send-imessage.runtime-0OEwzMQm.js +22 -0
  32. package/dist/plugin-sdk/deps-send-signal.runtime-BM1jRt3G.js +21 -0
  33. package/dist/plugin-sdk/deps-send-slack.runtime-1E3BYRdF.js +19 -0
  34. package/dist/plugin-sdk/deps-send-telegram.runtime-DNCxIflA.js +24 -0
  35. package/dist/plugin-sdk/deps-send-whatsapp.runtime-OLwr-9c8.js +57 -0
  36. package/dist/plugin-sdk/{diagnostic-LYUUmjJ5.js → diagnostic-Bxxu0ig-.js} +2 -2
  37. package/dist/plugin-sdk/{errors-CtMWwS2Z.js → errors-B3cHyZZA.js} +1 -1
  38. package/dist/plugin-sdk/{fetch-guard-CxYB5Kg6.js → fetch-guard-Dcgod0tg.js} +2 -2
  39. package/dist/plugin-sdk/{fs-safe-DtfhxbrI.js → fs-safe-BaKqI3G4.js} +3 -3
  40. package/dist/plugin-sdk/{image-BwjYjRHx.js → image-B2mQW9Rb.js} +6 -6
  41. package/dist/plugin-sdk/{image-ops-BnZKcbd6.js → image-ops-Cbzr4U9l.js} +2 -2
  42. package/dist/plugin-sdk/image-runtime-BFm45j49.js +25 -0
  43. package/dist/plugin-sdk/{ir-Z4hX67TJ.js → ir-ZEmrTr4J.js} +7 -7
  44. package/dist/plugin-sdk/{local-roots-KhjQw04O.js → local-roots-CIPRxA-4.js} +4 -4
  45. package/dist/plugin-sdk/{logger-DHIIvMxj.js → logger-CvPFVOgT.js} +2 -2
  46. package/dist/plugin-sdk/{login-C31642Ld.js → login-CCTew9bt.js} +4 -4
  47. package/dist/plugin-sdk/{login-qr--y2SG_Ue.js → login-qr-BI3Vi_wJ.js} +5 -5
  48. package/dist/plugin-sdk/{manager-2UZBMCc7.js → manager-BEoYPn7R.js} +8 -8
  49. package/dist/plugin-sdk/manager-runtime-DxclHQ4U.js +15 -0
  50. package/dist/plugin-sdk/{outbound-Ba0QUI5h.js → outbound-ByOw1K6W.js} +5 -5
  51. package/dist/plugin-sdk/{outbound-attachment-B1Laso-8.js → outbound-attachment-BzVhxRRw.js} +2 -2
  52. package/dist/plugin-sdk/{path-alias-guards-C7Vm5DZ1.js → path-alias-guards-sWayacde.js} +1 -1
  53. package/dist/plugin-sdk/{paths-DopV9PQG.js → paths-Dpg3qxcl.js} +1 -1
  54. package/dist/plugin-sdk/{pi-embedded-helpers-DnA_OCzP.js → pi-embedded-helpers-DIxXkGJf.js} +16 -16
  55. package/dist/plugin-sdk/{pi-model-discovery-DdPqXk8f.js → pi-model-discovery-DM_2uFtj.js} +1 -1
  56. package/dist/plugin-sdk/pi-model-discovery-runtime-BuzvkvNR.js +8 -0
  57. package/dist/plugin-sdk/{pi-tools.before-tool-call.runtime-DxFHiLUE.js → pi-tools.before-tool-call.runtime-w1dqL_ty.js} +4 -4
  58. package/dist/plugin-sdk/{plugins-CbCt4osF.js → plugins-C4USiH29.js} +4 -4
  59. package/dist/plugin-sdk/{proxy-env-C63mMdas.js → proxy-env-ET-rp8eg.js} +1 -1
  60. package/dist/plugin-sdk/{proxy-fetch-Ch95c_Y2.js → proxy-fetch-uDXGKG3Z.js} +1 -1
  61. package/dist/plugin-sdk/{pw-ai-DpJk62D4.js → pw-ai-CyOt3RDA.js} +9 -9
  62. package/dist/plugin-sdk/{qmd-manager-Ca-iSfEE.js → qmd-manager-BySdoVR7.js} +7 -7
  63. package/dist/plugin-sdk/{query-expansion-B_Xe41Ab.js → query-expansion-C6uS-7lj.js} +4 -4
  64. package/dist/plugin-sdk/{redact-hp9TOulW.js → redact-Bvxt1T_Q.js} +1 -1
  65. package/dist/plugin-sdk/{reply-CovBlFea.js → reply-CTCSeQqW.js} +73 -73
  66. package/dist/plugin-sdk/{resolve-outbound-target-BbrHgyUk.js → resolve-outbound-target-Bw8YNANu.js} +2 -2
  67. package/dist/plugin-sdk/{run-with-concurrency-BR1DXa8T.js → run-with-concurrency-C_KCHwvf.js} +1 -1
  68. package/dist/plugin-sdk/runtime-whatsapp-login.runtime-BxgRDkhc.js +10 -0
  69. package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-elOqrkfg.js +19 -0
  70. package/dist/plugin-sdk/{send-BvAtLLPl.js → send-BZ6nYFZr.js} +5 -5
  71. package/dist/plugin-sdk/{send-BTztm3D2.js → send-C0w6xP2x.js} +6 -6
  72. package/dist/plugin-sdk/{send-CWJUuG0i.js → send-CFf-1V89.js} +8 -8
  73. package/dist/plugin-sdk/{send-EcglC4cG.js → send-CY-Qfwia.js} +7 -7
  74. package/dist/plugin-sdk/{send-BXpXBwM_.js → send-qPyNGSe4.js} +13 -13
  75. package/dist/plugin-sdk/{session-k256LJZT.js → session-COrvpvUQ.js} +3 -3
  76. package/dist/plugin-sdk/signal.js +2 -2
  77. package/dist/plugin-sdk/{skill-commands-DoRqLzxm.js → skill-commands-DZqhtmiv.js} +4 -4
  78. package/dist/plugin-sdk/{skills-QudILG6e.js → skills-Cw_vXEJb.js} +6 -6
  79. package/dist/plugin-sdk/slash-commands.runtime-D67JLweo.js +13 -0
  80. package/dist/plugin-sdk/slash-dispatch.runtime-DvcpvCJ0.js +52 -0
  81. package/dist/plugin-sdk/slash-skill-commands.runtime-BM1x3azR.js +16 -0
  82. package/dist/plugin-sdk/{store-BbDQw3g6.js → store-CMHj6IIw.js} +2 -2
  83. package/dist/plugin-sdk/subagent-registry-runtime-1lbDyRzz.js +52 -0
  84. package/dist/plugin-sdk/{tables-BhvloMKN.js → tables-CSqrHsKL.js} +1 -1
  85. package/dist/plugin-sdk/{thinking-URzkT-3p.js → thinking-DOnsR_A8.js} +7 -7
  86. package/dist/plugin-sdk/{tokens-B1PW5Ayy.js → tokens-BDr0Z9o3.js} +1 -1
  87. package/dist/plugin-sdk/{tool-images-xpqbP6RR.js → tool-images-eEfOVkzf.js} +2 -2
  88. package/dist/plugin-sdk/web-BLyT64pW.js +56 -0
  89. package/dist/plugin-sdk/{whatsapp-actions-RcZ6vp61.js → whatsapp-actions-xcleMoMv.js} +17 -17
  90. package/dist/plugin-sdk/whatsapp.js +50 -50
  91. package/dist/{pw-ai-GcYO6HPE.js → pw-ai-CmphSzHx.js} +1 -1
  92. package/dist/{slash-dispatch.runtime-Dh053pQK.js → slash-dispatch.runtime-131yup2e.js} +6 -6
  93. package/dist/{subagent-registry-runtime-DSi5mnCQ.js → subagent-registry-runtime-DbSf_Je6.js} +6 -6
  94. package/dist/{web-1hWJDzNA.js → web-MR9d7KyB.js} +6 -6
  95. package/package.json +5 -2
  96. package/scripts/create-instance.sh +44 -19
  97. package/scripts/install-maca.sh +39 -28
  98. package/scripts/npm_publish.sh +8 -6
  99. package/ui/index.html +16 -0
  100. package/ui/node_modules/.bin/jiti +21 -0
  101. package/ui/node_modules/.bin/lessc +21 -0
  102. package/ui/node_modules/.bin/marked +21 -0
  103. package/ui/node_modules/.bin/playwright +21 -0
  104. package/ui/node_modules/.bin/sass +21 -0
  105. package/ui/node_modules/.bin/tsx +21 -0
  106. package/ui/node_modules/.bin/vite +21 -0
  107. package/ui/node_modules/.bin/vitest +21 -0
  108. package/ui/node_modules/.bin/yaml +21 -0
  109. package/ui/package.json +27 -0
  110. package/ui/public/apple-touch-icon.png +0 -0
  111. package/ui/public/favicon-32.png +0 -0
  112. package/ui/public/favicon.ico +0 -0
  113. package/ui/public/favicon.svg +22 -0
  114. package/ui/src/css.d.ts +1 -0
  115. package/ui/src/i18n/index.ts +3 -0
  116. package/ui/src/i18n/lib/lit-controller.ts +22 -0
  117. package/ui/src/i18n/lib/registry.ts +64 -0
  118. package/ui/src/i18n/lib/translate.ts +123 -0
  119. package/ui/src/i18n/lib/types.ts +9 -0
  120. package/ui/src/i18n/locales/de.ts +129 -0
  121. package/ui/src/i18n/locales/en.ts +337 -0
  122. package/ui/src/i18n/locales/pt-BR.ts +128 -0
  123. package/ui/src/i18n/locales/zh-CN.ts +330 -0
  124. package/ui/src/i18n/locales/zh-TW.ts +125 -0
  125. package/ui/src/i18n/test/translate.test.ts +56 -0
  126. package/ui/src/main.ts +2 -0
  127. package/ui/src/styles/base.css +385 -0
  128. package/ui/src/styles/chat/grouped.css +300 -0
  129. package/ui/src/styles/chat/layout.css +481 -0
  130. package/ui/src/styles/chat/sidebar.css +117 -0
  131. package/ui/src/styles/chat/text.css +146 -0
  132. package/ui/src/styles/chat/tool-cards.css +202 -0
  133. package/ui/src/styles/chat.css +5 -0
  134. package/ui/src/styles/components.css +2612 -0
  135. package/ui/src/styles/config.css +1658 -0
  136. package/ui/src/styles/layout.css +621 -0
  137. package/ui/src/styles/layout.mobile.css +374 -0
  138. package/ui/src/styles.css +5 -0
  139. package/ui/src/ui/__screenshots__/config-form.browser.test.ts/config-form-renderer-flags-unsupported-unions-1.png +0 -0
  140. package/ui/src/ui/__screenshots__/config-form.browser.test.ts/config-form-renderer-renders-inputs-and-patches-values-1.png +0 -0
  141. package/ui/src/ui/__screenshots__/config-form.browser.test.ts/config-form-renderer-renders-union-literals-as-select-options-1.png +0 -0
  142. package/ui/src/ui/__screenshots__/navigation.browser.test.ts/control-UI-routing-auto-scrolls-chat-history-to-the-latest-message-1.png +0 -0
  143. package/ui/src/ui/app-channels.ts +279 -0
  144. package/ui/src/ui/app-chat.ts +266 -0
  145. package/ui/src/ui/app-defaults.ts +50 -0
  146. package/ui/src/ui/app-events.ts +5 -0
  147. package/ui/src/ui/app-gateway.node.test.ts +229 -0
  148. package/ui/src/ui/app-gateway.ts +349 -0
  149. package/ui/src/ui/app-lifecycle.node.test.ts +44 -0
  150. package/ui/src/ui/app-lifecycle.ts +109 -0
  151. package/ui/src/ui/app-polling.ts +69 -0
  152. package/ui/src/ui/app-render-usage-tab.ts +273 -0
  153. package/ui/src/ui/app-render.helpers.node.test.ts +286 -0
  154. package/ui/src/ui/app-render.helpers.ts +574 -0
  155. package/ui/src/ui/app-render.ts +1168 -0
  156. package/ui/src/ui/app-scroll.test.ts +275 -0
  157. package/ui/src/ui/app-scroll.ts +179 -0
  158. package/ui/src/ui/app-settings.test.ts +70 -0
  159. package/ui/src/ui/app-settings.ts +440 -0
  160. package/ui/src/ui/app-tool-stream.node.test.ts +139 -0
  161. package/ui/src/ui/app-tool-stream.ts +455 -0
  162. package/ui/src/ui/app-view-state.ts +321 -0
  163. package/ui/src/ui/app.ts +621 -0
  164. package/ui/src/ui/assistant-identity.ts +23 -0
  165. package/ui/src/ui/chat/constants.ts +12 -0
  166. package/ui/src/ui/chat/copy-as-markdown.ts +97 -0
  167. package/ui/src/ui/chat/grouped-render.ts +287 -0
  168. package/ui/src/ui/chat/message-extract.test.ts +64 -0
  169. package/ui/src/ui/chat/message-extract.ts +122 -0
  170. package/ui/src/ui/chat/message-normalizer.test.ts +179 -0
  171. package/ui/src/ui/chat/message-normalizer.ts +101 -0
  172. package/ui/src/ui/chat/tool-cards.ts +156 -0
  173. package/ui/src/ui/chat/tool-helpers.test.ts +141 -0
  174. package/ui/src/ui/chat/tool-helpers.ts +37 -0
  175. package/ui/src/ui/chat-event-reload.test.ts +47 -0
  176. package/ui/src/ui/chat-event-reload.ts +16 -0
  177. package/ui/src/ui/chat-markdown.browser.test.ts +37 -0
  178. package/ui/src/ui/components/resizable-divider.ts +110 -0
  179. package/ui/src/ui/config-form.browser.test.ts +443 -0
  180. package/ui/src/ui/controllers/agent-files.ts +126 -0
  181. package/ui/src/ui/controllers/agent-identity.ts +59 -0
  182. package/ui/src/ui/controllers/agent-skills.ts +33 -0
  183. package/ui/src/ui/controllers/agents.test.ts +61 -0
  184. package/ui/src/ui/controllers/agents.ts +64 -0
  185. package/ui/src/ui/controllers/assistant-identity.ts +34 -0
  186. package/ui/src/ui/controllers/channels.ts +94 -0
  187. package/ui/src/ui/controllers/channels.types.ts +15 -0
  188. package/ui/src/ui/controllers/chat.test.ts +568 -0
  189. package/ui/src/ui/controllers/chat.ts +318 -0
  190. package/ui/src/ui/controllers/config/form-coerce.ts +160 -0
  191. package/ui/src/ui/controllers/config/form-utils.node.test.ts +455 -0
  192. package/ui/src/ui/controllers/config/form-utils.ts +90 -0
  193. package/ui/src/ui/controllers/config.test.ts +289 -0
  194. package/ui/src/ui/controllers/config.ts +219 -0
  195. package/ui/src/ui/controllers/control-ui-bootstrap.test.ts +82 -0
  196. package/ui/src/ui/controllers/control-ui-bootstrap.ts +49 -0
  197. package/ui/src/ui/controllers/cron-filters.test.ts +81 -0
  198. package/ui/src/ui/controllers/cron.test.ts +1070 -0
  199. package/ui/src/ui/controllers/cron.ts +921 -0
  200. package/ui/src/ui/controllers/debug.ts +60 -0
  201. package/ui/src/ui/controllers/devices.ts +159 -0
  202. package/ui/src/ui/controllers/exec-approval.ts +100 -0
  203. package/ui/src/ui/controllers/exec-approvals.ts +170 -0
  204. package/ui/src/ui/controllers/logs.ts +147 -0
  205. package/ui/src/ui/controllers/nodes.ts +32 -0
  206. package/ui/src/ui/controllers/presence.ts +37 -0
  207. package/ui/src/ui/controllers/sessions.test.ts +104 -0
  208. package/ui/src/ui/controllers/sessions.ts +127 -0
  209. package/ui/src/ui/controllers/skills.ts +157 -0
  210. package/ui/src/ui/controllers/usage.node.test.ts +181 -0
  211. package/ui/src/ui/controllers/usage.ts +315 -0
  212. package/ui/src/ui/data/moonshot-kimi-k2.ts +45 -0
  213. package/ui/src/ui/device-auth.ts +73 -0
  214. package/ui/src/ui/device-identity.ts +112 -0
  215. package/ui/src/ui/external-link.test.ts +18 -0
  216. package/ui/src/ui/external-link.ts +19 -0
  217. package/ui/src/ui/focus-mode.browser.test.ts +39 -0
  218. package/ui/src/ui/format.test.ts +101 -0
  219. package/ui/src/ui/format.ts +60 -0
  220. package/ui/src/ui/gateway.ts +360 -0
  221. package/ui/src/ui/icons.ts +256 -0
  222. package/ui/src/ui/markdown.test.ts +85 -0
  223. package/ui/src/ui/markdown.ts +139 -0
  224. package/ui/src/ui/navigation.browser.test.ts +188 -0
  225. package/ui/src/ui/navigation.test.ts +189 -0
  226. package/ui/src/ui/navigation.ts +165 -0
  227. package/ui/src/ui/open-external-url.test.ts +108 -0
  228. package/ui/src/ui/open-external-url.ts +73 -0
  229. package/ui/src/ui/presenter.ts +85 -0
  230. package/ui/src/ui/storage.node.test.ts +63 -0
  231. package/ui/src/ui/storage.ts +99 -0
  232. package/ui/src/ui/test-helpers/app-mount.ts +27 -0
  233. package/ui/src/ui/text-direction.test.ts +24 -0
  234. package/ui/src/ui/text-direction.ts +30 -0
  235. package/ui/src/ui/theme-transition.ts +109 -0
  236. package/ui/src/ui/theme.ts +16 -0
  237. package/ui/src/ui/tool-display.ts +159 -0
  238. package/ui/src/ui/types/chat-types.ts +44 -0
  239. package/ui/src/ui/types.ts +627 -0
  240. package/ui/src/ui/ui-types.ts +54 -0
  241. package/ui/src/ui/usage-helpers.node.test.ts +43 -0
  242. package/ui/src/ui/usage-helpers.ts +321 -0
  243. package/ui/src/ui/usage-types.ts +22 -0
  244. package/ui/src/ui/uuid.test.ts +41 -0
  245. package/ui/src/ui/uuid.ts +57 -0
  246. package/ui/src/ui/views/agents-panels-status-files.ts +461 -0
  247. package/ui/src/ui/views/agents-panels-tools-skills.browser.test.ts +102 -0
  248. package/ui/src/ui/views/agents-panels-tools-skills.ts +537 -0
  249. package/ui/src/ui/views/agents-utils.test.ts +100 -0
  250. package/ui/src/ui/views/agents-utils.ts +502 -0
  251. package/ui/src/ui/views/agents.ts +499 -0
  252. package/ui/src/ui/views/channel-config-extras.ts +49 -0
  253. package/ui/src/ui/views/channels.config.ts +155 -0
  254. package/ui/src/ui/views/channels.discord.ts +65 -0
  255. package/ui/src/ui/views/channels.googlechat.ts +79 -0
  256. package/ui/src/ui/views/channels.imessage.ts +65 -0
  257. package/ui/src/ui/views/channels.nostr-profile-form.ts +321 -0
  258. package/ui/src/ui/views/channels.nostr.ts +237 -0
  259. package/ui/src/ui/views/channels.shared.ts +38 -0
  260. package/ui/src/ui/views/channels.signal.ts +69 -0
  261. package/ui/src/ui/views/channels.slack.ts +65 -0
  262. package/ui/src/ui/views/channels.telegram.ts +120 -0
  263. package/ui/src/ui/views/channels.ts +325 -0
  264. package/ui/src/ui/views/channels.types.ts +62 -0
  265. package/ui/src/ui/views/channels.whatsapp.ts +118 -0
  266. package/ui/src/ui/views/chat-image-open.browser.test.ts +70 -0
  267. package/ui/src/ui/views/chat.test.ts +227 -0
  268. package/ui/src/ui/views/chat.ts +616 -0
  269. package/ui/src/ui/views/config-form.analyze.ts +267 -0
  270. package/ui/src/ui/views/config-form.node.ts +1073 -0
  271. package/ui/src/ui/views/config-form.render.ts +478 -0
  272. package/ui/src/ui/views/config-form.search.node.test.ts +69 -0
  273. package/ui/src/ui/views/config-form.shared.ts +96 -0
  274. package/ui/src/ui/views/config-form.ts +4 -0
  275. package/ui/src/ui/views/config-search.node.test.ts +50 -0
  276. package/ui/src/ui/views/config-search.ts +92 -0
  277. package/ui/src/ui/views/config.browser.test.ts +233 -0
  278. package/ui/src/ui/views/config.ts +820 -0
  279. package/ui/src/ui/views/cron.test.ts +741 -0
  280. package/ui/src/ui/views/cron.ts +1758 -0
  281. package/ui/src/ui/views/debug.ts +151 -0
  282. package/ui/src/ui/views/exec-approval.ts +89 -0
  283. package/ui/src/ui/views/gateway-url-confirmation.ts +40 -0
  284. package/ui/src/ui/views/instances.ts +89 -0
  285. package/ui/src/ui/views/logs.ts +155 -0
  286. package/ui/src/ui/views/markdown-sidebar.ts +40 -0
  287. package/ui/src/ui/views/nodes-exec-approvals.ts +617 -0
  288. package/ui/src/ui/views/nodes-shared.ts +67 -0
  289. package/ui/src/ui/views/nodes.ts +485 -0
  290. package/ui/src/ui/views/overview-hints.ts +16 -0
  291. package/ui/src/ui/views/overview.node.test.ts +39 -0
  292. package/ui/src/ui/views/overview.ts +361 -0
  293. package/ui/src/ui/views/sessions.test.ts +81 -0
  294. package/ui/src/ui/views/sessions.ts +321 -0
  295. package/ui/src/ui/views/skills-grouping.ts +40 -0
  296. package/ui/src/ui/views/skills-shared.ts +52 -0
  297. package/ui/src/ui/views/skills.ts +192 -0
  298. package/ui/src/ui/views/usage-metrics.ts +578 -0
  299. package/ui/src/ui/views/usage-query.ts +277 -0
  300. package/ui/src/ui/views/usage-render-details.test.ts +136 -0
  301. package/ui/src/ui/views/usage-render-details.ts +1083 -0
  302. package/ui/src/ui/views/usage-render-overview.ts +796 -0
  303. package/ui/src/ui/views/usage-styles/usageStyles-part1.ts +701 -0
  304. package/ui/src/ui/views/usage-styles/usageStyles-part2.ts +702 -0
  305. package/ui/src/ui/views/usage-styles/usageStyles-part3.ts +551 -0
  306. package/ui/src/ui/views/usage.ts +836 -0
  307. package/ui/src/ui/views/usageStyles.ts +5 -0
  308. package/ui/src/ui/views/usageTypes.ts +105 -0
  309. package/ui/vite.config.ts +43 -0
  310. package/ui/vitest.config.ts +15 -0
  311. package/ui/vitest.node.config.ts +10 -0
  312. package/dist/plugin-sdk/deliver-runtime-BFdqklJM.js +0 -32
  313. package/dist/plugin-sdk/deps-send-discord.runtime-DuqpYwU0.js +0 -23
  314. package/dist/plugin-sdk/deps-send-imessage.runtime-CZ2rS8Lb.js +0 -22
  315. package/dist/plugin-sdk/deps-send-signal.runtime-BdqiWhIh.js +0 -21
  316. package/dist/plugin-sdk/deps-send-slack.runtime-04s36qiC.js +0 -19
  317. package/dist/plugin-sdk/deps-send-telegram.runtime-LE5tkPvr.js +0 -24
  318. package/dist/plugin-sdk/deps-send-whatsapp.runtime-Bz57lobC.js +0 -57
  319. package/dist/plugin-sdk/image-runtime-B8twoubs.js +0 -25
  320. package/dist/plugin-sdk/manager-runtime-CMeLwose.js +0 -15
  321. package/dist/plugin-sdk/pi-model-discovery-runtime-D8CJhtJY.js +0 -8
  322. package/dist/plugin-sdk/runtime-whatsapp-login.runtime-SkO91TZH.js +0 -10
  323. package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-B0VWK5hm.js +0 -19
  324. package/dist/plugin-sdk/slash-commands.runtime-DS6vCNSL.js +0 -13
  325. package/dist/plugin-sdk/slash-dispatch.runtime-BXrxb2wd.js +0 -52
  326. package/dist/plugin-sdk/slash-skill-commands.runtime-Bd6qQ2oT.js +0 -16
  327. package/dist/plugin-sdk/subagent-registry-runtime-1uwQbuXj.js +0 -52
  328. package/dist/plugin-sdk/web-B74yhL2N.js +0 -56
@@ -0,0 +1,1073 @@
1
+ import { html, nothing, type TemplateResult } from "lit";
2
+ import type { ConfigUiHints } from "../types.ts";
3
+ import {
4
+ defaultValue,
5
+ hintForPath,
6
+ humanize,
7
+ pathKey,
8
+ schemaType,
9
+ type JsonSchema,
10
+ } from "./config-form.shared.ts";
11
+
12
+ const META_KEYS = new Set(["title", "description", "default", "nullable", "tags", "x-tags"]);
13
+
14
+ function isAnySchema(schema: JsonSchema): boolean {
15
+ const keys = Object.keys(schema ?? {}).filter((key) => !META_KEYS.has(key));
16
+ return keys.length === 0;
17
+ }
18
+
19
+ function jsonValue(value: unknown): string {
20
+ if (value === undefined) {
21
+ return "";
22
+ }
23
+ try {
24
+ return JSON.stringify(value, null, 2) ?? "";
25
+ } catch {
26
+ return "";
27
+ }
28
+ }
29
+
30
+ // SVG Icons as template literals
31
+ const icons = {
32
+ chevronDown: html`
33
+ <svg
34
+ viewBox="0 0 24 24"
35
+ fill="none"
36
+ stroke="currentColor"
37
+ stroke-width="2"
38
+ stroke-linecap="round"
39
+ stroke-linejoin="round"
40
+ >
41
+ <polyline points="6 9 12 15 18 9"></polyline>
42
+ </svg>
43
+ `,
44
+ plus: html`
45
+ <svg
46
+ viewBox="0 0 24 24"
47
+ fill="none"
48
+ stroke="currentColor"
49
+ stroke-width="2"
50
+ stroke-linecap="round"
51
+ stroke-linejoin="round"
52
+ >
53
+ <line x1="12" y1="5" x2="12" y2="19"></line>
54
+ <line x1="5" y1="12" x2="19" y2="12"></line>
55
+ </svg>
56
+ `,
57
+ minus: html`
58
+ <svg
59
+ viewBox="0 0 24 24"
60
+ fill="none"
61
+ stroke="currentColor"
62
+ stroke-width="2"
63
+ stroke-linecap="round"
64
+ stroke-linejoin="round"
65
+ >
66
+ <line x1="5" y1="12" x2="19" y2="12"></line>
67
+ </svg>
68
+ `,
69
+ trash: html`
70
+ <svg
71
+ viewBox="0 0 24 24"
72
+ fill="none"
73
+ stroke="currentColor"
74
+ stroke-width="2"
75
+ stroke-linecap="round"
76
+ stroke-linejoin="round"
77
+ >
78
+ <polyline points="3 6 5 6 21 6"></polyline>
79
+ <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
80
+ </svg>
81
+ `,
82
+ edit: html`
83
+ <svg
84
+ viewBox="0 0 24 24"
85
+ fill="none"
86
+ stroke="currentColor"
87
+ stroke-width="2"
88
+ stroke-linecap="round"
89
+ stroke-linejoin="round"
90
+ >
91
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
92
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
93
+ </svg>
94
+ `,
95
+ };
96
+
97
+ type FieldMeta = {
98
+ label: string;
99
+ help?: string;
100
+ tags: string[];
101
+ };
102
+
103
+ export type ConfigSearchCriteria = {
104
+ text: string;
105
+ tags: string[];
106
+ };
107
+
108
+ function hasSearchCriteria(criteria: ConfigSearchCriteria | undefined): boolean {
109
+ return Boolean(criteria && (criteria.text.length > 0 || criteria.tags.length > 0));
110
+ }
111
+
112
+ export function parseConfigSearchQuery(query: string): ConfigSearchCriteria {
113
+ const tags: string[] = [];
114
+ const seen = new Set<string>();
115
+ const raw = query.trim();
116
+ const stripped = raw.replace(/(^|\s)tag:([^\s]+)/gi, (_, leading: string, token: string) => {
117
+ const normalized = token.trim().toLowerCase();
118
+ if (normalized && !seen.has(normalized)) {
119
+ seen.add(normalized);
120
+ tags.push(normalized);
121
+ }
122
+ return leading;
123
+ });
124
+ return {
125
+ text: stripped.trim().toLowerCase(),
126
+ tags,
127
+ };
128
+ }
129
+
130
+ function normalizeTags(raw: unknown): string[] {
131
+ if (!Array.isArray(raw)) {
132
+ return [];
133
+ }
134
+ const seen = new Set<string>();
135
+ const tags: string[] = [];
136
+ for (const value of raw) {
137
+ if (typeof value !== "string") {
138
+ continue;
139
+ }
140
+ const tag = value.trim();
141
+ if (!tag) {
142
+ continue;
143
+ }
144
+ const key = tag.toLowerCase();
145
+ if (seen.has(key)) {
146
+ continue;
147
+ }
148
+ seen.add(key);
149
+ tags.push(tag);
150
+ }
151
+ return tags;
152
+ }
153
+
154
+ function resolveFieldMeta(
155
+ path: Array<string | number>,
156
+ schema: JsonSchema,
157
+ hints: ConfigUiHints,
158
+ ): FieldMeta {
159
+ const hint = hintForPath(path, hints);
160
+ const label = hint?.label ?? schema.title ?? humanize(String(path.at(-1)));
161
+ const help = hint?.help ?? schema.description;
162
+ const schemaTags = normalizeTags(schema["x-tags"] ?? schema.tags);
163
+ const hintTags = normalizeTags(hint?.tags);
164
+ return {
165
+ label,
166
+ help,
167
+ tags: hintTags.length > 0 ? hintTags : schemaTags,
168
+ };
169
+ }
170
+
171
+ function matchesText(text: string, candidates: Array<string | undefined>): boolean {
172
+ if (!text) {
173
+ return true;
174
+ }
175
+ for (const candidate of candidates) {
176
+ if (candidate && candidate.toLowerCase().includes(text)) {
177
+ return true;
178
+ }
179
+ }
180
+ return false;
181
+ }
182
+
183
+ function matchesTags(filterTags: string[], fieldTags: string[]): boolean {
184
+ if (filterTags.length === 0) {
185
+ return true;
186
+ }
187
+ const normalized = new Set(fieldTags.map((tag) => tag.toLowerCase()));
188
+ return filterTags.every((tag) => normalized.has(tag));
189
+ }
190
+
191
+ function matchesNodeSelf(params: {
192
+ schema: JsonSchema;
193
+ path: Array<string | number>;
194
+ hints: ConfigUiHints;
195
+ criteria: ConfigSearchCriteria;
196
+ }): boolean {
197
+ const { schema, path, hints, criteria } = params;
198
+ if (!hasSearchCriteria(criteria)) {
199
+ return true;
200
+ }
201
+ const { label, help, tags } = resolveFieldMeta(path, schema, hints);
202
+ if (!matchesTags(criteria.tags, tags)) {
203
+ return false;
204
+ }
205
+
206
+ if (!criteria.text) {
207
+ return true;
208
+ }
209
+
210
+ const pathLabel = path
211
+ .filter((segment): segment is string => typeof segment === "string")
212
+ .join(".");
213
+ const enumText =
214
+ schema.enum && schema.enum.length > 0
215
+ ? schema.enum.map((value) => String(value)).join(" ")
216
+ : "";
217
+
218
+ return matchesText(criteria.text, [
219
+ label,
220
+ help,
221
+ schema.title,
222
+ schema.description,
223
+ pathLabel,
224
+ enumText,
225
+ ]);
226
+ }
227
+
228
+ export function matchesNodeSearch(params: {
229
+ schema: JsonSchema;
230
+ value: unknown;
231
+ path: Array<string | number>;
232
+ hints: ConfigUiHints;
233
+ criteria: ConfigSearchCriteria;
234
+ }): boolean {
235
+ const { schema, value, path, hints, criteria } = params;
236
+ if (!hasSearchCriteria(criteria)) {
237
+ return true;
238
+ }
239
+ if (matchesNodeSelf({ schema, path, hints, criteria })) {
240
+ return true;
241
+ }
242
+
243
+ const type = schemaType(schema);
244
+ if (type === "object") {
245
+ const fallback = value ?? schema.default;
246
+ const obj =
247
+ fallback && typeof fallback === "object" && !Array.isArray(fallback)
248
+ ? (fallback as Record<string, unknown>)
249
+ : {};
250
+ const props = schema.properties ?? {};
251
+ for (const [propKey, node] of Object.entries(props)) {
252
+ if (
253
+ matchesNodeSearch({
254
+ schema: node,
255
+ value: obj[propKey],
256
+ path: [...path, propKey],
257
+ hints,
258
+ criteria,
259
+ })
260
+ ) {
261
+ return true;
262
+ }
263
+ }
264
+ const additional = schema.additionalProperties;
265
+ if (additional && typeof additional === "object") {
266
+ const reserved = new Set(Object.keys(props));
267
+ for (const [entryKey, entryValue] of Object.entries(obj)) {
268
+ if (reserved.has(entryKey)) {
269
+ continue;
270
+ }
271
+ if (
272
+ matchesNodeSearch({
273
+ schema: additional,
274
+ value: entryValue,
275
+ path: [...path, entryKey],
276
+ hints,
277
+ criteria,
278
+ })
279
+ ) {
280
+ return true;
281
+ }
282
+ }
283
+ }
284
+ return false;
285
+ }
286
+
287
+ if (type === "array") {
288
+ const itemsSchema = Array.isArray(schema.items) ? schema.items[0] : schema.items;
289
+ if (!itemsSchema) {
290
+ return false;
291
+ }
292
+ const arr = Array.isArray(value) ? value : Array.isArray(schema.default) ? schema.default : [];
293
+ if (arr.length === 0) {
294
+ return false;
295
+ }
296
+ for (let idx = 0; idx < arr.length; idx += 1) {
297
+ if (
298
+ matchesNodeSearch({
299
+ schema: itemsSchema,
300
+ value: arr[idx],
301
+ path: [...path, idx],
302
+ hints,
303
+ criteria,
304
+ })
305
+ ) {
306
+ return true;
307
+ }
308
+ }
309
+ }
310
+
311
+ return false;
312
+ }
313
+
314
+ function renderTags(tags: string[]): TemplateResult | typeof nothing {
315
+ if (tags.length === 0) {
316
+ return nothing;
317
+ }
318
+ return html`
319
+ <div class="cfg-tags">
320
+ ${tags.map((tag) => html`<span class="cfg-tag">${tag}</span>`)}
321
+ </div>
322
+ `;
323
+ }
324
+
325
+ export function renderNode(params: {
326
+ schema: JsonSchema;
327
+ value: unknown;
328
+ path: Array<string | number>;
329
+ hints: ConfigUiHints;
330
+ unsupported: Set<string>;
331
+ disabled: boolean;
332
+ showLabel?: boolean;
333
+ searchCriteria?: ConfigSearchCriteria;
334
+ onPatch: (path: Array<string | number>, value: unknown) => void;
335
+ }): TemplateResult | typeof nothing {
336
+ const { schema, value, path, hints, unsupported, disabled, onPatch } = params;
337
+ const showLabel = params.showLabel ?? true;
338
+ const type = schemaType(schema);
339
+ const { label, help, tags } = resolveFieldMeta(path, schema, hints);
340
+ const key = pathKey(path);
341
+ const criteria = params.searchCriteria;
342
+
343
+ if (unsupported.has(key)) {
344
+ return html`<div class="cfg-field cfg-field--error">
345
+ <div class="cfg-field__label">${label}</div>
346
+ <div class="cfg-field__error">Unsupported schema node. Use Raw mode.</div>
347
+ </div>`;
348
+ }
349
+ if (
350
+ criteria &&
351
+ hasSearchCriteria(criteria) &&
352
+ !matchesNodeSearch({ schema, value, path, hints, criteria })
353
+ ) {
354
+ return nothing;
355
+ }
356
+
357
+ // Handle anyOf/oneOf unions
358
+ if (schema.anyOf || schema.oneOf) {
359
+ const variants = schema.anyOf ?? schema.oneOf ?? [];
360
+ const nonNull = variants.filter(
361
+ (v) => !(v.type === "null" || (Array.isArray(v.type) && v.type.includes("null"))),
362
+ );
363
+
364
+ if (nonNull.length === 1) {
365
+ return renderNode({ ...params, schema: nonNull[0] });
366
+ }
367
+
368
+ // Check if it's a set of literal values (enum-like)
369
+ const extractLiteral = (v: JsonSchema): unknown => {
370
+ if (v.const !== undefined) {
371
+ return v.const;
372
+ }
373
+ if (v.enum && v.enum.length === 1) {
374
+ return v.enum[0];
375
+ }
376
+ return undefined;
377
+ };
378
+ const literals = nonNull.map(extractLiteral);
379
+ const allLiterals = literals.every((v) => v !== undefined);
380
+
381
+ if (allLiterals && literals.length > 0 && literals.length <= 5) {
382
+ // Use segmented control for small sets
383
+ const resolvedValue = value ?? schema.default;
384
+ return html`
385
+ <div class="cfg-field">
386
+ ${showLabel ? html`<label class="cfg-field__label">${label}</label>` : nothing}
387
+ ${help ? html`<div class="cfg-field__help">${help}</div>` : nothing}
388
+ ${renderTags(tags)}
389
+ <div class="cfg-segmented">
390
+ ${literals.map(
391
+ (lit) => html`
392
+ <button
393
+ type="button"
394
+ class="cfg-segmented__btn ${
395
+ // oxlint-disable typescript/no-base-to-string
396
+ lit === resolvedValue || String(lit) === String(resolvedValue) ? "active" : ""
397
+ }"
398
+ ?disabled=${disabled}
399
+ @click=${() => onPatch(path, lit)}
400
+ >
401
+ ${
402
+ // oxlint-disable typescript/no-base-to-string
403
+ String(lit)
404
+ }
405
+ </button>
406
+ `,
407
+ )}
408
+ </div>
409
+ </div>
410
+ `;
411
+ }
412
+
413
+ if (allLiterals && literals.length > 5) {
414
+ // Use dropdown for larger sets
415
+ return renderSelect({ ...params, options: literals, value: value ?? schema.default });
416
+ }
417
+
418
+ // Handle mixed primitive types
419
+ const primitiveTypes = new Set(nonNull.map((variant) => schemaType(variant)).filter(Boolean));
420
+ const normalizedTypes = new Set(
421
+ [...primitiveTypes].map((v) => (v === "integer" ? "number" : v)),
422
+ );
423
+
424
+ if ([...normalizedTypes].every((v) => ["string", "number", "boolean"].includes(v as string))) {
425
+ const hasString = normalizedTypes.has("string");
426
+ const hasNumber = normalizedTypes.has("number");
427
+ const hasBoolean = normalizedTypes.has("boolean");
428
+
429
+ if (hasBoolean && normalizedTypes.size === 1) {
430
+ return renderNode({
431
+ ...params,
432
+ schema: { ...schema, type: "boolean", anyOf: undefined, oneOf: undefined },
433
+ });
434
+ }
435
+
436
+ if (hasString || hasNumber) {
437
+ return renderTextInput({
438
+ ...params,
439
+ inputType: hasNumber && !hasString ? "number" : "text",
440
+ });
441
+ }
442
+ }
443
+ }
444
+
445
+ // Enum - use segmented for small, dropdown for large
446
+ if (schema.enum) {
447
+ const options = schema.enum;
448
+ if (options.length <= 5) {
449
+ const resolvedValue = value ?? schema.default;
450
+ return html`
451
+ <div class="cfg-field">
452
+ ${showLabel ? html`<label class="cfg-field__label">${label}</label>` : nothing}
453
+ ${help ? html`<div class="cfg-field__help">${help}</div>` : nothing}
454
+ ${renderTags(tags)}
455
+ <div class="cfg-segmented">
456
+ ${options.map(
457
+ (opt) => html`
458
+ <button
459
+ type="button"
460
+ class="cfg-segmented__btn ${opt === resolvedValue || String(opt) === String(resolvedValue) ? "active" : ""}"
461
+ ?disabled=${disabled}
462
+ @click=${() => onPatch(path, opt)}
463
+ >
464
+ ${String(opt)}
465
+ </button>
466
+ `,
467
+ )}
468
+ </div>
469
+ </div>
470
+ `;
471
+ }
472
+ return renderSelect({ ...params, options, value: value ?? schema.default });
473
+ }
474
+
475
+ // Object type - collapsible section
476
+ if (type === "object") {
477
+ return renderObject(params);
478
+ }
479
+
480
+ // Array type
481
+ if (type === "array") {
482
+ return renderArray(params);
483
+ }
484
+
485
+ // Boolean - toggle row
486
+ if (type === "boolean") {
487
+ const displayValue =
488
+ typeof value === "boolean"
489
+ ? value
490
+ : typeof schema.default === "boolean"
491
+ ? schema.default
492
+ : false;
493
+ return html`
494
+ <label class="cfg-toggle-row ${disabled ? "disabled" : ""}">
495
+ <div class="cfg-toggle-row__content">
496
+ <span class="cfg-toggle-row__label">${label}</span>
497
+ ${help ? html`<span class="cfg-toggle-row__help">${help}</span>` : nothing}
498
+ ${renderTags(tags)}
499
+ </div>
500
+ <div class="cfg-toggle">
501
+ <input
502
+ type="checkbox"
503
+ .checked=${displayValue}
504
+ ?disabled=${disabled}
505
+ @change=${(e: Event) => onPatch(path, (e.target as HTMLInputElement).checked)}
506
+ />
507
+ <span class="cfg-toggle__track"></span>
508
+ </div>
509
+ </label>
510
+ `;
511
+ }
512
+
513
+ // Number/Integer
514
+ if (type === "number" || type === "integer") {
515
+ return renderNumberInput(params);
516
+ }
517
+
518
+ // String
519
+ if (type === "string") {
520
+ return renderTextInput({ ...params, inputType: "text" });
521
+ }
522
+
523
+ // Fallback
524
+ return html`
525
+ <div class="cfg-field cfg-field--error">
526
+ <div class="cfg-field__label">${label}</div>
527
+ <div class="cfg-field__error">Unsupported type: ${type}. Use Raw mode.</div>
528
+ </div>
529
+ `;
530
+ }
531
+
532
+ function renderTextInput(params: {
533
+ schema: JsonSchema;
534
+ value: unknown;
535
+ path: Array<string | number>;
536
+ hints: ConfigUiHints;
537
+ disabled: boolean;
538
+ showLabel?: boolean;
539
+ searchCriteria?: ConfigSearchCriteria;
540
+ inputType: "text" | "number";
541
+ onPatch: (path: Array<string | number>, value: unknown) => void;
542
+ }): TemplateResult {
543
+ const { schema, value, path, hints, disabled, onPatch, inputType } = params;
544
+ const showLabel = params.showLabel ?? true;
545
+ const hint = hintForPath(path, hints);
546
+ const { label, help, tags } = resolveFieldMeta(path, schema, hints);
547
+ const isSensitive =
548
+ (hint?.sensitive ?? false) && !/^\$\{[^}]*\}$/.test(String(value ?? "").trim());
549
+ const placeholder =
550
+ hint?.placeholder ??
551
+ // oxlint-disable typescript/no-base-to-string
552
+ (isSensitive
553
+ ? "••••"
554
+ : schema.default !== undefined
555
+ ? `Default: ${String(schema.default)}`
556
+ : "");
557
+ const displayValue = value ?? "";
558
+
559
+ return html`
560
+ <div class="cfg-field">
561
+ ${showLabel ? html`<label class="cfg-field__label">${label}</label>` : nothing}
562
+ ${help ? html`<div class="cfg-field__help">${help}</div>` : nothing}
563
+ ${renderTags(tags)}
564
+ <div class="cfg-input-wrap">
565
+ <input
566
+ type=${isSensitive ? "password" : inputType}
567
+ class="cfg-input"
568
+ placeholder=${placeholder}
569
+ .value=${displayValue == null ? "" : String(displayValue)}
570
+ ?disabled=${disabled}
571
+ @input=${(e: Event) => {
572
+ const raw = (e.target as HTMLInputElement).value;
573
+ if (inputType === "number") {
574
+ if (raw.trim() === "") {
575
+ onPatch(path, undefined);
576
+ return;
577
+ }
578
+ const parsed = Number(raw);
579
+ onPatch(path, Number.isNaN(parsed) ? raw : parsed);
580
+ return;
581
+ }
582
+ onPatch(path, raw);
583
+ }}
584
+ @change=${(e: Event) => {
585
+ if (inputType === "number") {
586
+ return;
587
+ }
588
+ const raw = (e.target as HTMLInputElement).value;
589
+ onPatch(path, raw.trim());
590
+ }}
591
+ />
592
+ ${
593
+ schema.default !== undefined
594
+ ? html`
595
+ <button
596
+ type="button"
597
+ class="cfg-input__reset"
598
+ title="Reset to default"
599
+ ?disabled=${disabled}
600
+ @click=${() => onPatch(path, schema.default)}
601
+ >↺</button>
602
+ `
603
+ : nothing
604
+ }
605
+ </div>
606
+ </div>
607
+ `;
608
+ }
609
+
610
+ function renderNumberInput(params: {
611
+ schema: JsonSchema;
612
+ value: unknown;
613
+ path: Array<string | number>;
614
+ hints: ConfigUiHints;
615
+ disabled: boolean;
616
+ showLabel?: boolean;
617
+ searchCriteria?: ConfigSearchCriteria;
618
+ onPatch: (path: Array<string | number>, value: unknown) => void;
619
+ }): TemplateResult {
620
+ const { schema, value, path, hints, disabled, onPatch } = params;
621
+ const showLabel = params.showLabel ?? true;
622
+ const { label, help, tags } = resolveFieldMeta(path, schema, hints);
623
+ const displayValue = value ?? schema.default ?? "";
624
+ const numValue = typeof displayValue === "number" ? displayValue : 0;
625
+
626
+ return html`
627
+ <div class="cfg-field">
628
+ ${showLabel ? html`<label class="cfg-field__label">${label}</label>` : nothing}
629
+ ${help ? html`<div class="cfg-field__help">${help}</div>` : nothing}
630
+ ${renderTags(tags)}
631
+ <div class="cfg-number">
632
+ <button
633
+ type="button"
634
+ class="cfg-number__btn"
635
+ ?disabled=${disabled}
636
+ @click=${() => onPatch(path, numValue - 1)}
637
+ >−</button>
638
+ <input
639
+ type="number"
640
+ class="cfg-number__input"
641
+ .value=${displayValue == null ? "" : String(displayValue)}
642
+ ?disabled=${disabled}
643
+ @input=${(e: Event) => {
644
+ const raw = (e.target as HTMLInputElement).value;
645
+ const parsed = raw === "" ? undefined : Number(raw);
646
+ onPatch(path, parsed);
647
+ }}
648
+ />
649
+ <button
650
+ type="button"
651
+ class="cfg-number__btn"
652
+ ?disabled=${disabled}
653
+ @click=${() => onPatch(path, numValue + 1)}
654
+ >+</button>
655
+ </div>
656
+ </div>
657
+ `;
658
+ }
659
+
660
+ function renderSelect(params: {
661
+ schema: JsonSchema;
662
+ value: unknown;
663
+ path: Array<string | number>;
664
+ hints: ConfigUiHints;
665
+ disabled: boolean;
666
+ showLabel?: boolean;
667
+ searchCriteria?: ConfigSearchCriteria;
668
+ options: unknown[];
669
+ onPatch: (path: Array<string | number>, value: unknown) => void;
670
+ }): TemplateResult {
671
+ const { schema, value, path, hints, disabled, options, onPatch } = params;
672
+ const showLabel = params.showLabel ?? true;
673
+ const { label, help, tags } = resolveFieldMeta(path, schema, hints);
674
+ const resolvedValue = value ?? schema.default;
675
+ const currentIndex = options.findIndex(
676
+ (opt) => opt === resolvedValue || String(opt) === String(resolvedValue),
677
+ );
678
+ const unset = "__unset__";
679
+
680
+ return html`
681
+ <div class="cfg-field">
682
+ ${showLabel ? html`<label class="cfg-field__label">${label}</label>` : nothing}
683
+ ${help ? html`<div class="cfg-field__help">${help}</div>` : nothing}
684
+ ${renderTags(tags)}
685
+ <select
686
+ class="cfg-select"
687
+ ?disabled=${disabled}
688
+ .value=${currentIndex >= 0 ? String(currentIndex) : unset}
689
+ @change=${(e: Event) => {
690
+ const val = (e.target as HTMLSelectElement).value;
691
+ onPatch(path, val === unset ? undefined : options[Number(val)]);
692
+ }}
693
+ >
694
+ <option value=${unset}>Select...</option>
695
+ ${options.map(
696
+ (opt, idx) => html`
697
+ <option value=${String(idx)}>${String(opt)}</option>
698
+ `,
699
+ )}
700
+ </select>
701
+ </div>
702
+ `;
703
+ }
704
+
705
+ function renderObject(params: {
706
+ schema: JsonSchema;
707
+ value: unknown;
708
+ path: Array<string | number>;
709
+ hints: ConfigUiHints;
710
+ unsupported: Set<string>;
711
+ disabled: boolean;
712
+ showLabel?: boolean;
713
+ searchCriteria?: ConfigSearchCriteria;
714
+ onPatch: (path: Array<string | number>, value: unknown) => void;
715
+ }): TemplateResult {
716
+ const { schema, value, path, hints, unsupported, disabled, onPatch, searchCriteria } = params;
717
+ const showLabel = params.showLabel ?? true;
718
+ const { label, help, tags } = resolveFieldMeta(path, schema, hints);
719
+ const selfMatched =
720
+ searchCriteria && hasSearchCriteria(searchCriteria)
721
+ ? matchesNodeSelf({ schema, path, hints, criteria: searchCriteria })
722
+ : false;
723
+ const childSearchCriteria = selfMatched ? undefined : searchCriteria;
724
+
725
+ const fallback = value ?? schema.default;
726
+ const obj =
727
+ fallback && typeof fallback === "object" && !Array.isArray(fallback)
728
+ ? (fallback as Record<string, unknown>)
729
+ : {};
730
+ const props = schema.properties ?? {};
731
+ const entries = Object.entries(props);
732
+
733
+ // Sort by hint order
734
+ const sorted = entries.toSorted((a, b) => {
735
+ const orderA = hintForPath([...path, a[0]], hints)?.order ?? 0;
736
+ const orderB = hintForPath([...path, b[0]], hints)?.order ?? 0;
737
+ if (orderA !== orderB) {
738
+ return orderA - orderB;
739
+ }
740
+ return a[0].localeCompare(b[0]);
741
+ });
742
+
743
+ const reserved = new Set(Object.keys(props));
744
+ const additional = schema.additionalProperties;
745
+ const allowExtra = Boolean(additional) && typeof additional === "object";
746
+
747
+ const fields = html`
748
+ ${sorted.map(([propKey, node]) =>
749
+ renderNode({
750
+ schema: node,
751
+ value: obj[propKey],
752
+ path: [...path, propKey],
753
+ hints,
754
+ unsupported,
755
+ disabled,
756
+ searchCriteria: childSearchCriteria,
757
+ onPatch,
758
+ }),
759
+ )}
760
+ ${
761
+ allowExtra
762
+ ? renderMapField({
763
+ schema: additional,
764
+ value: obj,
765
+ path,
766
+ hints,
767
+ unsupported,
768
+ disabled,
769
+ reservedKeys: reserved,
770
+ searchCriteria: childSearchCriteria,
771
+ onPatch,
772
+ })
773
+ : nothing
774
+ }
775
+ `;
776
+
777
+ // For top-level, don't wrap in collapsible
778
+ if (path.length === 1) {
779
+ return html`
780
+ <div class="cfg-fields">
781
+ ${fields}
782
+ </div>
783
+ `;
784
+ }
785
+
786
+ if (!showLabel) {
787
+ return html`
788
+ <div class="cfg-fields cfg-fields--inline">
789
+ ${fields}
790
+ </div>
791
+ `;
792
+ }
793
+
794
+ // Nested objects get collapsible treatment
795
+ return html`
796
+ <details class="cfg-object" ?open=${path.length <= 2}>
797
+ <summary class="cfg-object__header">
798
+ <span class="cfg-object__title-wrap">
799
+ <span class="cfg-object__title">${label}</span>
800
+ ${renderTags(tags)}
801
+ </span>
802
+ <span class="cfg-object__chevron">${icons.chevronDown}</span>
803
+ </summary>
804
+ ${help ? html`<div class="cfg-object__help">${help}</div>` : nothing}
805
+ <div class="cfg-object__content">
806
+ ${fields}
807
+ </div>
808
+ </details>
809
+ `;
810
+ }
811
+
812
+ function renderArray(params: {
813
+ schema: JsonSchema;
814
+ value: unknown;
815
+ path: Array<string | number>;
816
+ hints: ConfigUiHints;
817
+ unsupported: Set<string>;
818
+ disabled: boolean;
819
+ showLabel?: boolean;
820
+ searchCriteria?: ConfigSearchCriteria;
821
+ onPatch: (path: Array<string | number>, value: unknown) => void;
822
+ }): TemplateResult {
823
+ const { schema, value, path, hints, unsupported, disabled, onPatch, searchCriteria } = params;
824
+ const showLabel = params.showLabel ?? true;
825
+ const { label, help, tags } = resolveFieldMeta(path, schema, hints);
826
+ const selfMatched =
827
+ searchCriteria && hasSearchCriteria(searchCriteria)
828
+ ? matchesNodeSelf({ schema, path, hints, criteria: searchCriteria })
829
+ : false;
830
+ const childSearchCriteria = selfMatched ? undefined : searchCriteria;
831
+
832
+ const itemsSchema = Array.isArray(schema.items) ? schema.items[0] : schema.items;
833
+ if (!itemsSchema) {
834
+ return html`
835
+ <div class="cfg-field cfg-field--error">
836
+ <div class="cfg-field__label">${label}</div>
837
+ <div class="cfg-field__error">Unsupported array schema. Use Raw mode.</div>
838
+ </div>
839
+ `;
840
+ }
841
+
842
+ const arr = Array.isArray(value) ? value : Array.isArray(schema.default) ? schema.default : [];
843
+
844
+ return html`
845
+ <div class="cfg-array">
846
+ <div class="cfg-array__header">
847
+ <div class="cfg-array__title">
848
+ ${showLabel ? html`<span class="cfg-array__label">${label}</span>` : nothing}
849
+ ${renderTags(tags)}
850
+ </div>
851
+ <span class="cfg-array__count">${arr.length} item${arr.length !== 1 ? "s" : ""}</span>
852
+ <button
853
+ type="button"
854
+ class="cfg-array__add"
855
+ ?disabled=${disabled}
856
+ @click=${() => {
857
+ const next = [...arr, defaultValue(itemsSchema)];
858
+ onPatch(path, next);
859
+ }}
860
+ >
861
+ <span class="cfg-array__add-icon">${icons.plus}</span>
862
+ Add
863
+ </button>
864
+ </div>
865
+ ${help ? html`<div class="cfg-array__help">${help}</div>` : nothing}
866
+
867
+ ${
868
+ arr.length === 0
869
+ ? html`
870
+ <div class="cfg-array__empty">No items yet. Click "Add" to create one.</div>
871
+ `
872
+ : html`
873
+ <div class="cfg-array__items">
874
+ ${arr.map(
875
+ (item, idx) => html`
876
+ <div class="cfg-array__item">
877
+ <div class="cfg-array__item-header">
878
+ <span class="cfg-array__item-index">#${idx + 1}</span>
879
+ <button
880
+ type="button"
881
+ class="cfg-array__item-remove"
882
+ title="Remove item"
883
+ ?disabled=${disabled}
884
+ @click=${() => {
885
+ const next = [...arr];
886
+ next.splice(idx, 1);
887
+ onPatch(path, next);
888
+ }}
889
+ >
890
+ ${icons.trash}
891
+ </button>
892
+ </div>
893
+ <div class="cfg-array__item-content">
894
+ ${renderNode({
895
+ schema: itemsSchema,
896
+ value: item,
897
+ path: [...path, idx],
898
+ hints,
899
+ unsupported,
900
+ disabled,
901
+ searchCriteria: childSearchCriteria,
902
+ showLabel: false,
903
+ onPatch,
904
+ })}
905
+ </div>
906
+ </div>
907
+ `,
908
+ )}
909
+ </div>
910
+ `
911
+ }
912
+ </div>
913
+ `;
914
+ }
915
+
916
+ function renderMapField(params: {
917
+ schema: JsonSchema;
918
+ value: Record<string, unknown>;
919
+ path: Array<string | number>;
920
+ hints: ConfigUiHints;
921
+ unsupported: Set<string>;
922
+ disabled: boolean;
923
+ reservedKeys: Set<string>;
924
+ searchCriteria?: ConfigSearchCriteria;
925
+ onPatch: (path: Array<string | number>, value: unknown) => void;
926
+ }): TemplateResult {
927
+ const {
928
+ schema,
929
+ value,
930
+ path,
931
+ hints,
932
+ unsupported,
933
+ disabled,
934
+ reservedKeys,
935
+ onPatch,
936
+ searchCriteria,
937
+ } = params;
938
+ const anySchema = isAnySchema(schema);
939
+ const entries = Object.entries(value ?? {}).filter(([key]) => !reservedKeys.has(key));
940
+ const visibleEntries =
941
+ searchCriteria && hasSearchCriteria(searchCriteria)
942
+ ? entries.filter(([key, entryValue]) =>
943
+ matchesNodeSearch({
944
+ schema,
945
+ value: entryValue,
946
+ path: [...path, key],
947
+ hints,
948
+ criteria: searchCriteria,
949
+ }),
950
+ )
951
+ : entries;
952
+
953
+ return html`
954
+ <div class="cfg-map">
955
+ <div class="cfg-map__header">
956
+ <span class="cfg-map__label">Custom entries</span>
957
+ <button
958
+ type="button"
959
+ class="cfg-map__add"
960
+ ?disabled=${disabled}
961
+ @click=${() => {
962
+ const next = { ...value };
963
+ let index = 1;
964
+ let key = `custom-${index}`;
965
+ while (key in next) {
966
+ index += 1;
967
+ key = `custom-${index}`;
968
+ }
969
+ next[key] = anySchema ? {} : defaultValue(schema);
970
+ onPatch(path, next);
971
+ }}
972
+ >
973
+ <span class="cfg-map__add-icon">${icons.plus}</span>
974
+ Add Entry
975
+ </button>
976
+ </div>
977
+
978
+ ${
979
+ visibleEntries.length === 0
980
+ ? html`
981
+ <div class="cfg-map__empty">No custom entries.</div>
982
+ `
983
+ : html`
984
+ <div class="cfg-map__items">
985
+ ${visibleEntries.map(([key, entryValue]) => {
986
+ const valuePath = [...path, key];
987
+ const fallback = jsonValue(entryValue);
988
+ return html`
989
+ <div class="cfg-map__item">
990
+ <div class="cfg-map__item-header">
991
+ <div class="cfg-map__item-key">
992
+ <input
993
+ type="text"
994
+ class="cfg-input cfg-input--sm"
995
+ placeholder="Key"
996
+ .value=${key}
997
+ ?disabled=${disabled}
998
+ @change=${(e: Event) => {
999
+ const nextKey = (e.target as HTMLInputElement).value.trim();
1000
+ if (!nextKey || nextKey === key) {
1001
+ return;
1002
+ }
1003
+ const next = { ...value };
1004
+ if (nextKey in next) {
1005
+ return;
1006
+ }
1007
+ next[nextKey] = next[key];
1008
+ delete next[key];
1009
+ onPatch(path, next);
1010
+ }}
1011
+ />
1012
+ </div>
1013
+ <button
1014
+ type="button"
1015
+ class="cfg-map__item-remove"
1016
+ title="Remove entry"
1017
+ ?disabled=${disabled}
1018
+ @click=${() => {
1019
+ const next = { ...value };
1020
+ delete next[key];
1021
+ onPatch(path, next);
1022
+ }}
1023
+ >
1024
+ ${icons.trash}
1025
+ </button>
1026
+ </div>
1027
+ <div class="cfg-map__item-value">
1028
+ ${
1029
+ anySchema
1030
+ ? html`
1031
+ <textarea
1032
+ class="cfg-textarea cfg-textarea--sm"
1033
+ placeholder="JSON value"
1034
+ rows="2"
1035
+ .value=${fallback}
1036
+ ?disabled=${disabled}
1037
+ @change=${(e: Event) => {
1038
+ const target = e.target as HTMLTextAreaElement;
1039
+ const raw = target.value.trim();
1040
+ if (!raw) {
1041
+ onPatch(valuePath, undefined);
1042
+ return;
1043
+ }
1044
+ try {
1045
+ onPatch(valuePath, JSON.parse(raw));
1046
+ } catch {
1047
+ target.value = fallback;
1048
+ }
1049
+ }}
1050
+ ></textarea>
1051
+ `
1052
+ : renderNode({
1053
+ schema,
1054
+ value: entryValue,
1055
+ path: valuePath,
1056
+ hints,
1057
+ unsupported,
1058
+ disabled,
1059
+ searchCriteria,
1060
+ showLabel: false,
1061
+ onPatch,
1062
+ })
1063
+ }
1064
+ </div>
1065
+ </div>
1066
+ `;
1067
+ })}
1068
+ </div>
1069
+ `
1070
+ }
1071
+ </div>
1072
+ `;
1073
+ }