@openhands/agent-canvas 1.8.0 → 1.9.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 (492) hide show
  1. package/README.md +1 -1
  2. package/README.windows.md +2 -2
  3. package/build/assets/{Trans-B7lYw4uD.js → Trans-Bwpzk1wT.js} +1 -1
  4. package/build/assets/{acp-providers-WUD4tfSq.js → acp-providers-C921BlYB.js} +1 -1
  5. package/build/assets/active-backend-context-BiwzkzDy.js +2 -0
  6. package/build/assets/agent-context-settings-DXNT_dcw.js +1 -0
  7. package/build/assets/agent-profiles-settings-BymJf3EP.js +1 -0
  8. package/build/assets/agent-server-conversation-service.api-CYSWw-2g.js +41 -0
  9. package/build/assets/agent-settings-BjgQQe-_.js +1 -0
  10. package/build/assets/{agent-settings-BGTyoT6X.js → agent-settings-WzqXk6B4.js} +2 -2
  11. package/build/assets/{alert-banner-eRyIwDyU.js → alert-banner-DR8EVyZM.js} +1 -1
  12. package/build/assets/api-error-message-e16L1kwH.js +1 -0
  13. package/build/assets/{api-key-entry-screen-JOx3SH1-.js → api-key-entry-screen-DI0I04O3.js} +1 -1
  14. package/build/assets/{app-settings-D53QZy5p.js → app-settings-DGistSFV.js} +1 -1
  15. package/build/assets/automation-C_eCrGyP.js +1 -0
  16. package/build/assets/automation-detail-DY6x2FW1.js +2 -0
  17. package/build/assets/automation-export-C09f-Lua.js +2 -0
  18. package/build/assets/automation-setup-route-CqKp0717.js +1 -0
  19. package/build/assets/automations-list-CqWJ7qXW.js +1 -0
  20. package/build/assets/{back-nav-button-C02hGa8t.js → back-nav-button-D4ngzpSh.js} +1 -1
  21. package/build/assets/backend-form-modal-B8D5hBzK.js +1 -0
  22. package/build/assets/{backend-synced-settings-badge-CQ9xtNWm.js → backend-synced-settings-badge-BnzI8Ysj.js} +1 -1
  23. package/build/assets/{badge-CzXbf7tR.js → badge-1Mf1iPcX.js} +1 -1
  24. package/build/assets/{base-modal-DkVyzFMt.js → base-modal-BBuy0WQQ.js} +1 -1
  25. package/build/assets/{brand-button-BUlDL-mz.js → brand-button-DGN6Y9o6.js} +1 -1
  26. package/build/assets/browser-D_ekTlTS.js +6 -0
  27. package/build/assets/{browser-tab-BijpUbdt.js → browser-tab-D4a4Ou_w.js} +1 -1
  28. package/build/assets/{chat-send-button-BKsVjhtA.js → chat-send-button-CgbcjD97.js} +1 -1
  29. package/build/assets/check-CPLXgluF.js +1 -0
  30. package/build/assets/chevron-down-CP4QHvyc.js +1 -0
  31. package/build/assets/circle-check-Dpy2_xSI.js +1 -0
  32. package/build/assets/{circle-plus-check-toggle-DdjUiOo1.js → circle-plus-check-toggle-yTAS-nX3.js} +1 -1
  33. package/build/assets/cloud-funnel-analytics-CEOl5Gxr.js +1 -0
  34. package/build/assets/{combobox-caret-Dl9UtvD6.js → combobox-caret-Bd2yySnF.js} +1 -1
  35. package/build/assets/command-menu-Bj6X4ckE.js +1 -0
  36. package/build/assets/condenser-settings-EAXQNR2E.js +1 -0
  37. package/build/assets/{confirmation-modal-CRBJSllP.js → confirmation-modal-NCUPr2-O.js} +1 -1
  38. package/build/assets/{context-menu-list-item-FGaafXGh.js → context-menu-list-item-CrCrYuPF.js} +1 -1
  39. package/build/assets/conversation-BzyIcSwr.js +1 -0
  40. package/build/assets/conversation-CUJ0Vo8g.js +90 -0
  41. package/build/assets/conversation-panel-BzyIcSwr.js +1 -0
  42. package/build/assets/conversation-state-store-F62HvuI1.js +1 -0
  43. package/build/assets/{conversation-tab-empty-state-CDjvNgpG.js → conversation-tab-empty-state-BCZ7rUMH.js} +1 -1
  44. package/build/assets/{copy-Drwu97Gx.js → copy-DCmZtz6n.js} +1 -1
  45. package/build/assets/{copy-to-clipboard-button-NbN1clVr.js → copy-to-clipboard-button-D_Lp6-Fl.js} +1 -1
  46. package/build/assets/{device-verify-CDm_WrVu.js → device-verify-DLmolX8Q.js} +1 -1
  47. package/build/assets/{dropdown-classes-CqA2BTCL.js → dropdown-classes-BB8rzqXL.js} +1 -1
  48. package/build/assets/{entry.client-BO2KQj0y.js → entry.client-BbIYOsIg.js} +2 -2
  49. package/build/assets/{enum-filter-dropdown-DSqPZnsD.js → enum-filter-dropdown-C08-srev.js} +1 -1
  50. package/build/assets/{environment-switch-overlay-mGl2pqqx.js → environment-switch-overlay-BepBBX66.js} +1 -1
  51. package/build/assets/{extensions-hub-Br3Lgynp.js → extensions-hub-R3KNv-5V.js} +1 -1
  52. package/build/assets/{extensions-navigation-BQmstKDZ.js → extensions-navigation-CfWFmI1I.js} +1 -1
  53. package/build/assets/{external-link-D9B2QMPf.js → external-link-BdBSg8rk.js} +1 -1
  54. package/build/assets/files-tab-DEq8MbHu.js +1 -0
  55. package/build/assets/{form-control-classes-CtVAktAy.js → form-control-classes-DAfsCJTe.js} +1 -1
  56. package/build/assets/{format-time-delta-DICE64sz.js → format-time-delta-CxfPJpzC.js} +1 -1
  57. package/build/assets/git-provider-icon-BZqqUPlm.js +1 -0
  58. package/build/assets/git-repo-dropdown-9WKINV6J.js +1 -0
  59. package/build/assets/git-repo-dropdown-DEpOwUez.js +1 -0
  60. package/build/assets/{highlighted-source-view-KMsA9c4a.js → highlighted-source-view-CX9oIoVT.js} +1 -1
  61. package/build/assets/home-CY0JaxiY.js +1 -0
  62. package/build/assets/home-CaNWlVvB.js +1 -0
  63. package/build/assets/home-store-Ch1ErdBc.js +1 -0
  64. package/build/assets/index-home-CaNWlVvB.js +1 -0
  65. package/build/assets/info-CmWQXtRQ.js +1 -0
  66. package/build/assets/install-server-modal-BWrudmZU.js +1 -0
  67. package/build/assets/{launch-DjkKw55l.js → launch-B1POpHuP.js} +1 -1
  68. package/build/assets/{list-todo-BpAf7aOT.js → list-todo-DtiL3lTp.js} +1 -1
  69. package/build/assets/llm-not-configured-banner-3tMsM4K6.js +27 -0
  70. package/build/assets/{llm-settings-D8zNkIlP.js → llm-settings-CXo8iPNW.js} +1 -1
  71. package/build/assets/llm-settings-D0W8mJDk.js +1 -0
  72. package/build/assets/{llm-subscription-service-COTgZc1C.js → llm-subscription-service-DBJbfd8Z.js} +1 -1
  73. package/build/assets/loader-circle-B4QDBARx.js +1 -0
  74. package/build/assets/{loading-spinner-DRFyh6Iy.js → loading-spinner-DfZ_bhV7.js} +1 -1
  75. package/build/assets/{manage-backends-modal-DiRWE3tJ.js → manage-backends-modal-CwyWsiag.js} +1 -1
  76. package/build/assets/manifest-39f4789d.js +1 -0
  77. package/build/assets/markdown-renderer-DgiLLhW6.js +8 -0
  78. package/build/assets/mcp-DXG9LWrI.js +9 -0
  79. package/build/assets/mcp-marketplace-utils-DFmhKc5s.js +1 -0
  80. package/build/assets/messages-kBjKaAeA.js +40 -0
  81. package/build/assets/{modal-body-C3bGicn4.js → modal-body-CMsDpalT.js} +1 -1
  82. package/build/assets/{modal-classes-DFFO6BMU.js → modal-classes-B6PDrxXo.js} +1 -1
  83. package/build/assets/{modal-close-button-vxkAIRyZ.js → modal-close-button-DyxBZaAb.js} +1 -1
  84. package/build/assets/{onboarding-DWuFieEA.js → onboarding-47hh2IHA.js} +1 -1
  85. package/build/assets/{onboarding-modal-C8m72Dpb.js → onboarding-modal-9BuvvROo.js} +1 -1
  86. package/build/assets/{organization-service.api-CQ73CB-S.js → organization-service.api-UDy63-oC.js} +1 -1
  87. package/build/assets/{pencil-DzW2GB2M.js → pencil-BjgtZmWp.js} +1 -1
  88. package/build/assets/{planner-tab-YG0bGxDY.js → planner-tab-BWk8885v.js} +1 -1
  89. package/build/assets/{plugins-management-service-ijhH8OSh.js → plugins-management-service-CjkgF2dd.js} +1 -1
  90. package/build/assets/plus-DrKuuvMe.js +1 -0
  91. package/build/assets/{profiles-service.api-C32pPm70.js → profiles-service.api-CukSFLzz.js} +1 -1
  92. package/build/assets/{providers-DuSn9s2i.js → providers-CqUWC85T.js} +1 -1
  93. package/build/assets/{proxy-BiyDhMBd.js → proxy-BBUG3sd2.js} +1 -1
  94. package/build/assets/{puzzle-DlqT0yvr.js → puzzle-BoNc6zZ8.js} +1 -1
  95. package/build/assets/recommended-automations-launcher-ejxy1P27.js +1 -0
  96. package/build/assets/{refresh-cw-Br5254f4.js → refresh-cw-BrM5ZAZD.js} +1 -1
  97. package/build/assets/root-5S-M8SYa.js +2 -0
  98. package/build/assets/root-DZ39lS5f.css +1 -0
  99. package/build/assets/root-layout-BPqoa0gv.js +3 -0
  100. package/build/assets/{schema-field-htfNouTG.js → schema-field-nImDoY6-.js} +1 -1
  101. package/build/assets/{sdk-section-page-D0K1dBQY.js → sdk-section-page-BxMrtUNV.js} +1 -1
  102. package/build/assets/search-Dc2qR4O2.js +1 -0
  103. package/build/assets/{secrets-service-Dbv5Zb8G.js → secrets-service-Dhjh3HhH.js} +1 -1
  104. package/build/assets/{secrets-settings-Da4SSaX9.js → secrets-settings-CPdxceT_.js} +1 -1
  105. package/build/assets/segmented-toggle-Do6oyepX.js +1 -0
  106. package/build/assets/{settings-F4a09IxY.js → settings-BRbnfKEm.js} +1 -1
  107. package/build/assets/{settings-dropdown-input-jOiv5Yrk.js → settings-dropdown-input-DYCULKpp.js} +1 -1
  108. package/build/assets/{settings-index-CyJBf5NA.js → settings-index-C9o2NBO0.js} +1 -1
  109. package/build/assets/settings-input-B-q2gkVy.js +1 -0
  110. package/build/assets/{settings-list-classes-VqMzkYYt.js → settings-list-classes-WpQi0q1L.js} +1 -1
  111. package/build/assets/{settings-modal-C-S1NpbC.js → settings-modal-CEK0AIx6.js} +1 -1
  112. package/build/assets/{settings-service.api-CTJRrvMu.js → settings-service.api-CZiPD8__.js} +1 -1
  113. package/build/assets/{settings-switch-B405oU_v.js → settings-switch-LqKdGxHL.js} +1 -1
  114. package/build/assets/shared-conversation-Bu-dQlgp.js +1 -0
  115. package/build/assets/{sidebar-layout-BGMuyC86.js → sidebar-layout-oHBGQmLh.js} +1 -1
  116. package/build/assets/{sidebar-mobile-menu-toggle-xiKoUfGa.js → sidebar-mobile-menu-toggle-ND-hgygu.js} +1 -1
  117. package/build/assets/{sidebar-nav-link-DA4_ct9G.js → sidebar-nav-link-CrujNA09.js} +1 -1
  118. package/build/assets/{sidebar-store-FRJ3rADB.js → sidebar-store-Bui-WNEr.js} +1 -1
  119. package/build/assets/{skill-card-pill-row-BMdhr_sd.js → skill-card-pill-row-CZBI7OqH.js} +1 -1
  120. package/build/assets/{skill-icon-badge-CGUkgcVH.js → skill-icon-badge-BOov_phZ.js} +1 -1
  121. package/build/assets/{skills-plugins-C5eJnyft.js → skills-plugins-D0PiHWAD.js} +1 -1
  122. package/build/assets/{skills-settings-pUxMr6WZ.js → skills-settings-B43KArIz.js} +2 -2
  123. package/build/assets/{styled-tooltip-B3kIdVyH.js → styled-tooltip-C7grwnOX.js} +1 -1
  124. package/build/assets/{task-list-tab-B3k4W1cK.js → task-list-tab-R3ac6m7O.js} +1 -1
  125. package/build/assets/{terminal-CBI_41Zc.js → terminal-yqkNdEAx.js} +1 -1
  126. package/build/assets/{toggle-switch-DYRr9CAR.js → toggle-switch-BeSrbgCg.js} +1 -1
  127. package/build/assets/{trash-2-gZPlx23R.js → trash-2-wW5TWcdN.js} +1 -1
  128. package/build/assets/{typography-B1UxuuEC.js → typography-iYhzLlHO.js} +1 -1
  129. package/build/assets/{use-acp-credential-form-CjSvKWFH.js → use-acp-credential-form-DUxbxmbq.js} +1 -1
  130. package/build/assets/{use-activate-agent-profile-DCRf47e-.js → use-activate-agent-profile-CDHA52tC.js} +1 -1
  131. package/build/assets/use-agent-profiles-TsOdIyw5.js +1 -0
  132. package/build/assets/{use-can-manage-org-profiles-vBiP7rHF.js → use-can-manage-org-profiles-Bt1M71u5.js} +1 -1
  133. package/build/assets/{use-cloud-current-user-id-BMq9gw93.js → use-cloud-current-user-id-DWt7HCgf.js} +1 -1
  134. package/build/assets/use-config-CxlEdf_v.js +1 -0
  135. package/build/assets/{use-create-conversation-CakhLF0X.js → use-create-conversation-nOJ6Um5h.js} +1 -1
  136. package/build/assets/{use-create-secret-RT4QtSS9.js → use-create-secret-Dj5ang9x.js} +1 -1
  137. package/build/assets/use-is-authed-BT7Iud4U.js +1 -0
  138. package/build/assets/{use-is-creating-conversation-CIAcnd8M.js → use-is-creating-conversation-CAggrM75.js} +1 -1
  139. package/build/assets/use-llm-profiles-2Wc2db8u.js +1 -0
  140. package/build/assets/{use-onboarding-completion-CJU4MNAY.js → use-onboarding-completion-gxQlcEy2.js} +1 -1
  141. package/build/assets/{use-plugins-marketplace-D-93nqtJ.js → use-plugins-marketplace-CVO7sXYR.js} +1 -1
  142. package/build/assets/use-save-agent-profile-D0wC5ExW.js +1 -0
  143. package/build/assets/{use-save-settings--C6LZXfI.js → use-save-settings-L3aMaDeS.js} +1 -1
  144. package/build/assets/{use-settings-DIAo5KFk.js → use-settings-8nt3DGxW.js} +1 -1
  145. package/build/assets/use-settings-nav-items-DHAAD-zk.js +16 -0
  146. package/build/assets/{use-skills-D_mLhYNa.js → use-skills-CE6miQ3r.js} +1 -1
  147. package/build/assets/use-tracking-BGboadaZ.js +68 -0
  148. package/build/assets/{use-user-conversation-DhcAEyJX.js → use-user-conversation-BIIpGUgM.js} +1 -1
  149. package/build/assets/useInfiniteQuery-BLRVmVPU.js +1 -0
  150. package/build/assets/{useMutation-DHLpZ6J0.js → useMutation-CaSHLnX0.js} +1 -1
  151. package/build/assets/{useQueries-B3T1a7qt.js → useQueries--d_WWAzc.js} +1 -1
  152. package/build/assets/{useQuery-B38jBavx.js → useQuery-DiPh8LRJ.js} +1 -1
  153. package/build/assets/utils-BgGG1znF.js +1 -0
  154. package/build/assets/{vendor~browser-CIoZCqGs.js → vendor~browser-IVYgIgG1.js} +1 -1
  155. package/build/assets/{vendor~entry.client-Di4qO77Y.js → vendor~entry.client-BcYIVQvB.js} +1 -1
  156. package/build/assets/{vendor~entry.client-BfviqoVc.js → vendor~entry.client-CXM9oiAk.js} +1 -1
  157. package/build/assets/{vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~g4xrpilz-CYPMCkX1.js → vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~b0bpqexo-CbTboogo.js} +1 -1
  158. package/build/assets/{vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~f9xdmd0z-DLMP7bZx.js → vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~d4by102p-C2f0ZhS-.js} +1 -1
  159. package/build/assets/{vendor~files-tab-atebg0iG.js → vendor~files-tab-C7RmQJwN.js} +1 -1
  160. package/build/assets/{vendor~index-home~home~conversation-panel~conversation-G9DMrR7m.js → vendor~index-home~home~conversation-panel~conversation-Bqp1ZqJM.js} +1 -1
  161. package/build/assets/{vendor~root-layout~conversation-panel~conversation-DEhW1h3I.js → vendor~root-layout~conversation-panel~conversation-BCn8lzeG.js} +1 -1
  162. package/build/assets/vendor~root-layout~index-home~home~conversation-panel~conversation~automation-setup-route-B_L-gcOG.js +1 -0
  163. package/build/assets/vendor~root-layout~index-home~home~conversation-panel~conversation~shared-conversation-sjx0x5ac.js +1 -0
  164. package/build/assets/vendor~root-layout~index-home~home~mcp~automations-list~automation-setup-route~onboarding-modal-D7IDevqN.js +1 -0
  165. package/build/assets/{vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~bi2r7bko-VFnwct3q.js → vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~dfc8ddxy-tTyxnjXT.js} +1 -1
  166. package/build/assets/{vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~hakxo2tj-ClV0p9g3.js → vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~gy8zbn65-CGt6bWRT.js} +1 -1
  167. package/build/assets/{vendor~root-layout~index-home~home~conversation-panel~conversation~launch~skills-settings~a~eywn6ulz-BKxxr74w.js → vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~skills-setti~dufan6ng-Dtdw8H0q.js} +17 -4
  168. package/build/assets/{verification-settings-Vr5cN4md.js → verification-settings-DBsnSXC3.js} +1 -1
  169. package/build/assets/x-omAzpXkl.js +1 -0
  170. package/build/index.html +4 -4
  171. package/build/locales/ar/openhands.json +37 -2
  172. package/build/locales/ca/openhands.json +37 -2
  173. package/build/locales/de/openhands.json +37 -2
  174. package/build/locales/en/openhands.json +38 -3
  175. package/build/locales/es/openhands.json +37 -2
  176. package/build/locales/fr/openhands.json +37 -2
  177. package/build/locales/it/openhands.json +37 -2
  178. package/build/locales/ja/openhands.json +37 -2
  179. package/build/locales/ko-KR/openhands.json +37 -2
  180. package/build/locales/no/openhands.json +37 -2
  181. package/build/locales/pt/openhands.json +37 -2
  182. package/build/locales/tr/openhands.json +37 -2
  183. package/build/locales/uk/openhands.json +37 -2
  184. package/build/locales/zh-CN/openhands.json +37 -2
  185. package/build/locales/zh-TW/openhands.json +37 -2
  186. package/config/defaults.json +1 -1
  187. package/dist/api/agent-server-adapter.cjs +3 -3
  188. package/dist/api/agent-server-adapter.cjs.map +1 -1
  189. package/dist/api/agent-server-adapter.d.ts +46 -6
  190. package/dist/api/agent-server-adapter.js +187 -168
  191. package/dist/api/agent-server-adapter.js.map +1 -1
  192. package/dist/api/agent-server-compatibility.cjs +1 -1
  193. package/dist/api/agent-server-compatibility.cjs.map +1 -1
  194. package/dist/api/agent-server-compatibility.d.ts +4 -0
  195. package/dist/api/agent-server-compatibility.js +41 -38
  196. package/dist/api/agent-server-compatibility.js.map +1 -1
  197. package/dist/api/automation-service/automation-service.api.cjs +1 -1
  198. package/dist/api/automation-service/automation-service.api.cjs.map +1 -1
  199. package/dist/api/automation-service/automation-service.api.d.ts +20 -0
  200. package/dist/api/automation-service/automation-service.api.js +99 -62
  201. package/dist/api/automation-service/automation-service.api.js.map +1 -1
  202. package/dist/components/conversation-events/chat/event-content-helpers/get-action-event-title.cjs +2 -0
  203. package/dist/components/conversation-events/chat/event-content-helpers/get-action-event-title.cjs.map +1 -0
  204. package/dist/components/conversation-events/chat/event-content-helpers/get-action-event-title.d.ts +12 -0
  205. package/dist/components/conversation-events/chat/event-content-helpers/get-action-event-title.js +94 -0
  206. package/dist/components/conversation-events/chat/event-content-helpers/get-action-event-title.js.map +1 -0
  207. package/dist/components/conversation-events/chat/event-content-helpers/get-event-content.cjs +1 -1
  208. package/dist/components/conversation-events/chat/event-content-helpers/get-event-content.cjs.map +1 -1
  209. package/dist/components/conversation-events/chat/event-content-helpers/get-event-content.js +31 -90
  210. package/dist/components/conversation-events/chat/event-content-helpers/get-event-content.js.map +1 -1
  211. package/dist/components/features/automations/recommended-automations-launcher.d.ts +0 -7
  212. package/dist/components/features/automations/recommended-automations-section.d.ts +0 -6
  213. package/dist/components/features/backends/backend-form-modal.cjs +1 -1
  214. package/dist/components/features/backends/backend-form-modal.cjs.map +1 -1
  215. package/dist/components/features/backends/backend-form-modal.js +568 -358
  216. package/dist/components/features/backends/backend-form-modal.js.map +1 -1
  217. package/dist/components/features/backends/device-flow-auth.cjs +1 -1
  218. package/dist/components/features/backends/device-flow-auth.cjs.map +1 -1
  219. package/dist/components/features/backends/device-flow-auth.d.ts +9 -1
  220. package/dist/components/features/backends/device-flow-auth.js +56 -55
  221. package/dist/components/features/backends/device-flow-auth.js.map +1 -1
  222. package/dist/components/features/backends/manage-backends-modal.cjs +1 -1
  223. package/dist/components/features/backends/manage-backends-modal.cjs.map +1 -1
  224. package/dist/components/features/backends/manage-backends-modal.js +1 -1
  225. package/dist/components/features/backends/manage-backends-modal.js.map +1 -1
  226. package/dist/components/features/chat/chat-interface.cjs +1 -1
  227. package/dist/components/features/chat/chat-interface.cjs.map +1 -1
  228. package/dist/components/features/chat/chat-interface.js +105 -105
  229. package/dist/components/features/chat/chat-interface.js.map +1 -1
  230. package/dist/components/features/chat/chat-message.cjs +1 -1
  231. package/dist/components/features/chat/chat-message.cjs.map +1 -1
  232. package/dist/components/features/chat/chat-message.d.ts +2 -1
  233. package/dist/components/features/chat/chat-message.js +59 -49
  234. package/dist/components/features/chat/chat-message.js.map +1 -1
  235. package/dist/components/features/chat/pending-user-messages.cjs +1 -1
  236. package/dist/components/features/chat/pending-user-messages.cjs.map +1 -1
  237. package/dist/components/features/chat/pending-user-messages.js +4 -1
  238. package/dist/components/features/chat/pending-user-messages.js.map +1 -1
  239. package/dist/components/features/chat/typing-indicator.cjs +1 -1
  240. package/dist/components/features/chat/typing-indicator.cjs.map +1 -1
  241. package/dist/components/features/chat/typing-indicator.d.ts +8 -1
  242. package/dist/components/features/chat/typing-indicator.js +94 -10
  243. package/dist/components/features/chat/typing-indicator.js.map +1 -1
  244. package/dist/components/features/command-menu/command-menu-items.d.ts +5 -1
  245. package/dist/components/features/files-tab/segmented-toggle.cjs +1 -1
  246. package/dist/components/features/files-tab/segmented-toggle.cjs.map +1 -1
  247. package/dist/components/features/files-tab/segmented-toggle.d.ts +2 -0
  248. package/dist/components/features/files-tab/segmented-toggle.js +17 -14
  249. package/dist/components/features/files-tab/segmented-toggle.js.map +1 -1
  250. package/dist/components/features/manifest/manifest-form-field.d.ts +27 -0
  251. package/dist/components/features/manifest/manifest-prerequisites-step.d.ts +11 -0
  252. package/dist/components/features/manifest/manifest-review-step.d.ts +14 -0
  253. package/dist/components/features/manifest/manifest-setup-dialog.d.ts +14 -0
  254. package/dist/components/features/settings/settings-input.cjs +1 -1
  255. package/dist/components/features/settings/settings-input.cjs.map +1 -1
  256. package/dist/components/features/settings/settings-input.d.ts +6 -0
  257. package/dist/components/features/settings/settings-input.js +13 -8
  258. package/dist/components/features/settings/settings-input.js.map +1 -1
  259. package/dist/components/features/sidebar/sidebar-rail-body.cjs +1 -1
  260. package/dist/components/features/sidebar/sidebar-rail-body.cjs.map +1 -1
  261. package/dist/components/features/sidebar/sidebar-rail-body.js +124 -123
  262. package/dist/components/features/sidebar/sidebar-rail-body.js.map +1 -1
  263. package/dist/config/defaults.cjs +1 -1
  264. package/dist/config/defaults.cjs.map +1 -1
  265. package/dist/config/defaults.js +1 -1
  266. package/dist/config/defaults.js.map +1 -1
  267. package/dist/hooks/query/query-keys.cjs.map +1 -1
  268. package/dist/hooks/query/query-keys.d.ts +4 -0
  269. package/dist/hooks/query/query-keys.js.map +1 -1
  270. package/dist/hooks/query/use-manifest-capabilities.d.ts +21 -0
  271. package/dist/hooks/query/use-manifest-prerequisites.d.ts +24 -0
  272. package/dist/hooks/use-llm-configured.cjs +1 -1
  273. package/dist/hooks/use-llm-configured.cjs.map +1 -1
  274. package/dist/hooks/use-llm-configured.js +2 -1
  275. package/dist/hooks/use-llm-configured.js.map +1 -1
  276. package/dist/hooks/use-manifest-preflight.d.ts +18 -0
  277. package/dist/hooks/use-tracking.cjs +1 -1
  278. package/dist/hooks/use-tracking.cjs.map +1 -1
  279. package/dist/hooks/use-tracking.d.ts +14 -0
  280. package/dist/hooks/use-tracking.js +19 -1
  281. package/dist/hooks/use-tracking.js.map +1 -1
  282. package/dist/i18n/declaration.cjs +1 -1
  283. package/dist/i18n/declaration.cjs.map +1 -1
  284. package/dist/i18n/declaration.d.ts +36 -1
  285. package/dist/i18n/declaration.js +1 -1
  286. package/dist/i18n/declaration.js.map +1 -1
  287. package/dist/i18n/translation.cjs +2 -2
  288. package/dist/i18n/translation.cjs.map +1 -1
  289. package/dist/i18n/translation.js +611 -16
  290. package/dist/i18n/translation.js.map +1 -1
  291. package/dist/icons/external-link.cjs +2 -0
  292. package/dist/icons/external-link.cjs.map +1 -0
  293. package/dist/icons/external-link.js +22 -0
  294. package/dist/icons/external-link.js.map +1 -0
  295. package/dist/icons/server.cjs +2 -0
  296. package/dist/icons/server.cjs.map +1 -0
  297. package/dist/icons/server.js +17 -0
  298. package/dist/icons/server.js.map +1 -0
  299. package/dist/locales/ar/openhands.json +37 -2
  300. package/dist/locales/ca/openhands.json +37 -2
  301. package/dist/locales/de/openhands.json +37 -2
  302. package/dist/locales/en/openhands.json +38 -3
  303. package/dist/locales/es/openhands.json +37 -2
  304. package/dist/locales/fr/openhands.json +37 -2
  305. package/dist/locales/it/openhands.json +37 -2
  306. package/dist/locales/ja/openhands.json +37 -2
  307. package/dist/locales/ko-KR/openhands.json +37 -2
  308. package/dist/locales/no/openhands.json +37 -2
  309. package/dist/locales/pt/openhands.json +37 -2
  310. package/dist/locales/tr/openhands.json +37 -2
  311. package/dist/locales/uk/openhands.json +37 -2
  312. package/dist/locales/zh-CN/openhands.json +37 -2
  313. package/dist/locales/zh-TW/openhands.json +37 -2
  314. package/dist/manifests/automation-interface.cjs +2 -0
  315. package/dist/manifests/automation-interface.cjs.map +1 -0
  316. package/dist/manifests/automation-interface.d.ts +44 -0
  317. package/dist/manifests/automation-interface.js +81 -0
  318. package/dist/manifests/automation-interface.js.map +1 -0
  319. package/dist/manifests/automation-setup.cjs +2 -0
  320. package/dist/manifests/automation-setup.cjs.map +1 -0
  321. package/dist/manifests/automation-setup.d.ts +52 -0
  322. package/dist/manifests/automation-setup.js +8 -0
  323. package/dist/manifests/automation-setup.js.map +1 -0
  324. package/dist/manifests/interface-validation.cjs +2 -0
  325. package/dist/manifests/interface-validation.cjs.map +1 -0
  326. package/dist/manifests/interface-validation.d.ts +27 -0
  327. package/dist/manifests/interface-validation.js +182 -0
  328. package/dist/manifests/interface-validation.js.map +1 -0
  329. package/dist/manifests/manifest-actions.d.ts +15 -0
  330. package/dist/manifests/manifest-capabilities.d.ts +25 -0
  331. package/dist/manifests/manifest-error-map.d.ts +40 -0
  332. package/dist/manifests/manifest-local-validation.d.ts +56 -0
  333. package/dist/manifests/manifest-registry.cjs +2 -0
  334. package/dist/manifests/manifest-registry.cjs.map +1 -0
  335. package/dist/manifests/manifest-registry.d.ts +15 -0
  336. package/dist/manifests/manifest-registry.js +26 -0
  337. package/dist/manifests/manifest-registry.js.map +1 -0
  338. package/dist/manifests/manifest-sources.cjs +2 -0
  339. package/dist/manifests/manifest-sources.cjs.map +1 -0
  340. package/dist/manifests/manifest-sources.d.ts +15 -0
  341. package/dist/manifests/manifest-sources.js +8 -0
  342. package/dist/manifests/manifest-sources.js.map +1 -0
  343. package/dist/manifests/manifest-template.d.ts +18 -0
  344. package/dist/manifests/manifest-validation.cjs +2 -0
  345. package/dist/manifests/manifest-validation.cjs.map +1 -0
  346. package/dist/manifests/manifest-validation.d.ts +30 -0
  347. package/dist/manifests/manifest-validation.js +145 -0
  348. package/dist/manifests/manifest-validation.js.map +1 -0
  349. package/dist/manifests/types.cjs +2 -0
  350. package/dist/manifests/types.cjs.map +1 -0
  351. package/dist/manifests/types.d.ts +227 -0
  352. package/dist/manifests/types.js +6 -0
  353. package/dist/manifests/types.js.map +1 -0
  354. package/dist/node_modules/@openhands/extensions/automations/catalog/github-pr-reviewer/manifest.cjs +9 -0
  355. package/dist/node_modules/@openhands/extensions/automations/catalog/github-pr-reviewer/manifest.cjs.map +1 -0
  356. package/dist/node_modules/@openhands/extensions/automations/catalog/github-pr-reviewer/manifest.js +75 -0
  357. package/dist/node_modules/@openhands/extensions/automations/catalog/github-pr-reviewer/manifest.js.map +1 -0
  358. package/dist/node_modules/@openhands/extensions/automations/catalog/github-repo-monitor/manifest.cjs +9 -0
  359. package/dist/node_modules/@openhands/extensions/automations/catalog/github-repo-monitor/manifest.cjs.map +1 -0
  360. package/dist/node_modules/@openhands/extensions/automations/catalog/github-repo-monitor/manifest.js +78 -0
  361. package/dist/node_modules/@openhands/extensions/automations/catalog/github-repo-monitor/manifest.js.map +1 -0
  362. package/dist/node_modules/@openhands/extensions/automations/catalog/incident-retrospective-drafter/manifest.cjs +17 -0
  363. package/dist/node_modules/@openhands/extensions/automations/catalog/incident-retrospective-drafter/manifest.cjs.map +1 -0
  364. package/dist/node_modules/@openhands/extensions/automations/catalog/incident-retrospective-drafter/manifest.js +89 -0
  365. package/dist/node_modules/@openhands/extensions/automations/catalog/incident-retrospective-drafter/manifest.js.map +1 -0
  366. package/dist/node_modules/@openhands/extensions/automations/catalog/jira-issue-to-pr/manifest.cjs +10 -0
  367. package/dist/node_modules/@openhands/extensions/automations/catalog/jira-issue-to-pr/manifest.cjs.map +1 -0
  368. package/dist/node_modules/@openhands/extensions/automations/catalog/jira-issue-to-pr/manifest.js +17 -0
  369. package/dist/node_modules/@openhands/extensions/automations/catalog/jira-issue-to-pr/manifest.js.map +1 -0
  370. package/dist/node_modules/@openhands/extensions/automations/catalog/linear-triage-assistant/manifest.cjs +9 -0
  371. package/dist/node_modules/@openhands/extensions/automations/catalog/linear-triage-assistant/manifest.cjs.map +1 -0
  372. package/dist/node_modules/@openhands/extensions/automations/catalog/linear-triage-assistant/manifest.js +15 -0
  373. package/dist/node_modules/@openhands/extensions/automations/catalog/linear-triage-assistant/manifest.js.map +1 -0
  374. package/dist/node_modules/@openhands/extensions/automations/catalog/research-brief-writer/manifest.cjs +9 -0
  375. package/dist/node_modules/@openhands/extensions/automations/catalog/research-brief-writer/manifest.cjs.map +1 -0
  376. package/dist/node_modules/@openhands/extensions/automations/catalog/research-brief-writer/manifest.js +18 -0
  377. package/dist/node_modules/@openhands/extensions/automations/catalog/research-brief-writer/manifest.js.map +1 -0
  378. package/dist/node_modules/@openhands/extensions/automations/catalog/slack-channel-monitor/manifest.cjs +9 -0
  379. package/dist/node_modules/@openhands/extensions/automations/catalog/slack-channel-monitor/manifest.cjs.map +1 -0
  380. package/dist/node_modules/@openhands/extensions/automations/catalog/slack-channel-monitor/manifest.js +14 -0
  381. package/dist/node_modules/@openhands/extensions/automations/catalog/slack-channel-monitor/manifest.js.map +1 -0
  382. package/dist/node_modules/@openhands/extensions/automations/catalog/slack-standup-digest/manifest.cjs +9 -0
  383. package/dist/node_modules/@openhands/extensions/automations/catalog/slack-standup-digest/manifest.cjs.map +1 -0
  384. package/dist/node_modules/@openhands/extensions/automations/catalog/slack-standup-digest/manifest.js +14 -0
  385. package/dist/node_modules/@openhands/extensions/automations/catalog/slack-standup-digest/manifest.js.map +1 -0
  386. package/dist/node_modules/@openhands/extensions/automations/catalog-index.cjs +2 -0
  387. package/dist/node_modules/@openhands/extensions/automations/catalog-index.cjs.map +1 -0
  388. package/dist/node_modules/@openhands/extensions/automations/catalog-index.js +23 -0
  389. package/dist/node_modules/@openhands/extensions/automations/catalog-index.js.map +1 -0
  390. package/dist/node_modules/@openhands/extensions/automations/index.cjs +2 -0
  391. package/dist/node_modules/@openhands/extensions/automations/index.cjs.map +1 -0
  392. package/dist/node_modules/@openhands/extensions/automations/index.js +18 -0
  393. package/dist/node_modules/@openhands/extensions/automations/index.js.map +1 -0
  394. package/dist/node_modules/@openhands/extensions/automations/interface.cjs +2 -0
  395. package/dist/node_modules/@openhands/extensions/automations/interface.cjs.map +1 -0
  396. package/dist/node_modules/@openhands/extensions/automations/interface.js +89 -0
  397. package/dist/node_modules/@openhands/extensions/automations/interface.js.map +1 -0
  398. package/dist/node_modules/@openhands/extensions/integrations/catalog/datadog.cjs +1 -1
  399. package/dist/node_modules/@openhands/extensions/integrations/catalog/datadog.cjs.map +1 -1
  400. package/dist/node_modules/@openhands/extensions/integrations/catalog/datadog.js +3 -3
  401. package/dist/node_modules/@openhands/extensions/integrations/catalog/datadog.js.map +1 -1
  402. package/dist/node_modules/@openhands/extensions/skills/index.cjs +17 -4
  403. package/dist/node_modules/@openhands/extensions/skills/index.cjs.map +1 -1
  404. package/dist/node_modules/@openhands/extensions/skills/index.js +5 -5
  405. package/dist/node_modules/@openhands/extensions/skills/index.js.map +1 -1
  406. package/dist/node_modules/lucide-react/dist/esm/icons/monitor.cjs +2 -0
  407. package/dist/node_modules/lucide-react/dist/esm/icons/monitor.cjs.map +1 -0
  408. package/dist/node_modules/lucide-react/dist/esm/icons/monitor.js +29 -0
  409. package/dist/node_modules/lucide-react/dist/esm/icons/monitor.js.map +1 -0
  410. package/dist/package.cjs +1 -1
  411. package/dist/package.cjs.map +1 -1
  412. package/dist/package.js +2 -2
  413. package/dist/package.js.map +1 -1
  414. package/dist/types/agent-server/core/events/conversation-state-event.d.ts +2 -19
  415. package/dist/types/agent-server/core/events/observation-event.d.ts +2 -18
  416. package/dist/types/automation.d.ts +7 -3
  417. package/dist/utils/api-error-message.d.ts +6 -2
  418. package/dist/utils/automation-catalog.cjs +2 -0
  419. package/dist/utils/automation-catalog.cjs.map +1 -0
  420. package/dist/utils/automation-catalog.d.ts +28 -0
  421. package/dist/utils/automation-catalog.js +5 -0
  422. package/dist/utils/automation-catalog.js.map +1 -0
  423. package/dist/utils/automation-timeout.d.ts +3 -2
  424. package/dist/utils/responder-deployment.d.ts +5 -1
  425. package/dist/utils/transcript-export/index.cjs +5 -5
  426. package/dist/utils/transcript-export/index.js +12 -12
  427. package/package.json +2 -2
  428. package/scripts/check-translation-completeness.cjs +1 -0
  429. package/scripts/dev-safe.mjs +15 -10
  430. package/scripts/dev-static.mjs +17 -0
  431. package/scripts/dev-with-automation.mjs +12 -14
  432. package/scripts/ingress.mjs +22 -0
  433. package/scripts/proxy-utils.mjs +157 -2
  434. package/scripts/runtime-services-info.mjs +6 -6
  435. package/scripts/static-build.mjs +0 -7
  436. package/scripts/static-server.mjs +10 -0
  437. package/build/assets/active-backend-context-mY7Gff5c.js +0 -2
  438. package/build/assets/agent-context-settings-BTUmWEzL.js +0 -1
  439. package/build/assets/agent-profiles-settings-Ilqid_R5.js +0 -1
  440. package/build/assets/agent-server-conversation-service.api-C_7YnjzP.js +0 -41
  441. package/build/assets/agent-settings-CxfT4ksO.js +0 -1
  442. package/build/assets/automation-BGP_I1RR.js +0 -1
  443. package/build/assets/automation-detail-nEflClmc.js +0 -2
  444. package/build/assets/automation-export-BO_kfqC8.js +0 -2
  445. package/build/assets/automations-list-BYncC7gv.js +0 -1
  446. package/build/assets/backend-form-modal-CKrBF80P.js +0 -1
  447. package/build/assets/browser-3QjrEr1D.js +0 -6
  448. package/build/assets/check-CrxQfJZ2.js +0 -1
  449. package/build/assets/chevron-down-D_LsyTA7.js +0 -1
  450. package/build/assets/circle-check-DZn9HkLE.js +0 -1
  451. package/build/assets/cloud-funnel-analytics-NlmDoeo2.js +0 -1
  452. package/build/assets/command-menu-CvLPs7dg.js +0 -1
  453. package/build/assets/condenser-settings-C4Y9CzQV.js +0 -1
  454. package/build/assets/conversation-D_ORtpKC.js +0 -1
  455. package/build/assets/conversation-Dpc5nmGi.js +0 -90
  456. package/build/assets/conversation-panel-D_ORtpKC.js +0 -1
  457. package/build/assets/conversation-state-store-CGRQemtx.js +0 -1
  458. package/build/assets/files-tab-4c9HFSLo.js +0 -1
  459. package/build/assets/git-provider-icon-0PqmIS_S.js +0 -1
  460. package/build/assets/home-D9JsyEmE.js +0 -1
  461. package/build/assets/home-TFhnmZLx.js +0 -1
  462. package/build/assets/index-home-D9JsyEmE.js +0 -1
  463. package/build/assets/install-server-modal-BgGRnqA0.js +0 -1
  464. package/build/assets/llm-not-configured-banner-Y4W9-sar.js +0 -27
  465. package/build/assets/llm-settings-nPwv7hKu.js +0 -1
  466. package/build/assets/loader-circle-CGD6-Vak.js +0 -1
  467. package/build/assets/manifest-1cf33a7b.js +0 -1
  468. package/build/assets/markdown-renderer-U40HF0Ak.js +0 -8
  469. package/build/assets/mcp-xDMaHzOw.js +0 -9
  470. package/build/assets/messages-CdNfZPgd.js +0 -40
  471. package/build/assets/plus-CTGLo3aa.js +0 -1
  472. package/build/assets/recommended-automations-launcher-Dru1yN46.js +0 -58
  473. package/build/assets/root-B5JXKp1P.css +0 -1
  474. package/build/assets/root-DOLLQteU.js +0 -2
  475. package/build/assets/root-layout-xmylWoew.js +0 -3
  476. package/build/assets/search-BrpWXWUi.js +0 -1
  477. package/build/assets/segmented-toggle-CZKOdIly.js +0 -1
  478. package/build/assets/settings-input-BEaTCem1.js +0 -1
  479. package/build/assets/shared-conversation-BvWtTwES.js +0 -1
  480. package/build/assets/use-agent-profiles-DmT9zxAN.js +0 -1
  481. package/build/assets/use-config-C959QcRC.js +0 -1
  482. package/build/assets/use-is-authed-BsS4XwSL.js +0 -1
  483. package/build/assets/use-llm-profiles-CdaxVpyY.js +0 -1
  484. package/build/assets/use-save-agent-profile-DGJ3-u0M.js +0 -1
  485. package/build/assets/use-settings-nav-items-DPK5sxvR.js +0 -16
  486. package/build/assets/use-tracking-COqZLobY.js +0 -1
  487. package/build/assets/utils-DsWhWn2F.js +0 -1
  488. package/build/assets/vendor~root-layout~index-home~home~conversation-panel~conversation-Acmf1sKj.js +0 -1
  489. package/build/assets/vendor~root-layout~index-home~home~conversation-panel~conversation~shared-conversation-CmzCY1BT.js +0 -1
  490. package/build/assets/vendor~root-layout~index-home~home~mcp~automations-list~onboarding-modal-C7Yh77RT.js +0 -1
  491. package/build/assets/x-Dx_JbpHA.js +0 -1
  492. /package/build/assets/{vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~f1hl7voa-BrmgjeOw.js → vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~emdr2jt4-BrmgjeOw.js} +0 -0
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The automation interface seam.
3
+ *
4
+ * Every automation-specific datum the host's surfaces render — routes,
5
+ * navigation and page copy, the settable attributes, the import/export
6
+ * envelope, endpoint paths, the featured and responder id lists — is served
7
+ * from here.
8
+ * The published interface manifest supplies it when the pinned
9
+ * `@openhands/extensions` ships one and it passes admission; otherwise the
10
+ * host's own defaults do, which reproduce today's behavior exactly. Copy
11
+ * defaults are null, meaning "render the host's own translation".
12
+ */
13
+ import type { AutomationAttributeName, InterfaceEndpointName, InterfaceImportExport } from "./types";
14
+ export interface AttributeSpec {
15
+ present: boolean;
16
+ /** Null means "render the host's own translation". */
17
+ label: string | null;
18
+ help: string | null;
19
+ required: boolean;
20
+ min: number | null;
21
+ max: number | null;
22
+ }
23
+ export declare function automationListPath(): string;
24
+ export declare function automationSetupPath(id: string): string;
25
+ export declare function automationDetailPath(id: string): string;
26
+ export declare function getAutomationEndpoint(name: InterfaceEndpointName): string;
27
+ /** An id-parameterized endpoint with `{id}` substituted, encoded. */
28
+ export declare function getAutomationIdEndpoint(name: "detail" | "dispatch" | "runs" | "tarball", id: string): string;
29
+ export interface InterfaceCopy {
30
+ sidebarLabel: string | null;
31
+ commandMenuTitle: string | null;
32
+ commandMenuDescription: string | null;
33
+ commandMenuKeywords: string | null;
34
+ listTitle: string | null;
35
+ listSubtitle: string | null;
36
+ detailBackLabel: string | null;
37
+ editTitle: string | null;
38
+ }
39
+ export declare function getInterfaceCopy(): InterfaceCopy;
40
+ export declare function getAttributeSpec(name: AutomationAttributeName): AttributeSpec;
41
+ export declare function getImportExportSpec(): InterfaceImportExport;
42
+ export declare function getAutomationsDocsUrl(): string;
43
+ export declare function getFeaturedAutomationIds(): readonly string[];
44
+ export declare function getResponderIntegrationIds(): readonly string[];
@@ -0,0 +1,81 @@
1
+ import e from "../node_modules/@openhands/extensions/automations/index.js";
2
+ import { validateInterfaceManifest as t } from "./interface-validation.js";
3
+ import { AUTOMATION_INTERFACE_CANDIDATE as n } from "./manifest-sources.js";
4
+ //#region src/manifests/automation-interface.ts
5
+ var r = {
6
+ list: "/automations",
7
+ setup: "/automations/new/:automationId",
8
+ detail: "/automations/:automationId"
9
+ }, i = {
10
+ list: "/v1",
11
+ detail: "/v1/{id}",
12
+ dispatch: "/v1/{id}/dispatch",
13
+ runs: "/v1/{id}/runs",
14
+ tarball: "/v1/{id}/tarball",
15
+ health: "/health",
16
+ capabilities: "/v1/capabilities",
17
+ validate: "/v1/validate",
18
+ createPrompt: "/v1/preset/prompt",
19
+ createPlugin: "/v1/preset/plugin"
20
+ }, a = {
21
+ fileKind: "automation",
22
+ fileVersion: 1,
23
+ filenameSuffix: ".automation.json",
24
+ importDefaults: {
25
+ repoProvider: "github",
26
+ placeholderEventSource: "agent-canvas-import"
27
+ }
28
+ };
29
+ function o(e) {
30
+ return typeof e == "object" && !!e && !Array.isArray(e);
31
+ }
32
+ function s() {
33
+ return new Set(e.flatMap((e) => o(e) && typeof e.id == "string" ? [e.id] : []));
34
+ }
35
+ function c(e) {
36
+ if (e === void 0) return null;
37
+ let n = t(e, {
38
+ catalogIds: s(),
39
+ mountedRoutes: r
40
+ });
41
+ return n.valid ? e : (console.warn("Rejected the automation interface manifest:", n.errors.join("; ")), null);
42
+ }
43
+ var l = c(n);
44
+ function u() {
45
+ return (l?.routes ?? r).list;
46
+ }
47
+ function d(e) {
48
+ return (l?.endpoints ?? i)[e];
49
+ }
50
+ function f(e, t) {
51
+ return d(e).replace("{id}", encodeURIComponent(t));
52
+ }
53
+ var p = {
54
+ sidebarLabel: null,
55
+ commandMenuTitle: null,
56
+ commandMenuDescription: null,
57
+ commandMenuKeywords: null,
58
+ listTitle: null,
59
+ listSubtitle: null,
60
+ detailBackLabel: null,
61
+ editTitle: null
62
+ };
63
+ function m() {
64
+ return l ? {
65
+ sidebarLabel: l.navigation.sidebar.label,
66
+ commandMenuTitle: l.navigation.commandMenu.title,
67
+ commandMenuDescription: l.navigation.commandMenu.description,
68
+ commandMenuKeywords: l.navigation.commandMenu.keywords,
69
+ listTitle: l.pages.list.title,
70
+ listSubtitle: l.pages.list.subtitle,
71
+ detailBackLabel: l.pages.detail.backLabel,
72
+ editTitle: l.pages.edit.title
73
+ } : p;
74
+ }
75
+ function h() {
76
+ return l?.importExport ?? a;
77
+ }
78
+ //#endregion
79
+ export { u as automationListPath, d as getAutomationEndpoint, f as getAutomationIdEndpoint, h as getImportExportSpec, m as getInterfaceCopy };
80
+
81
+ //# sourceMappingURL=automation-interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"automation-interface.js","names":[],"sources":["../../src/manifests/automation-interface.ts"],"sourcesContent":["/**\n * The automation interface seam.\n *\n * Every automation-specific datum the host's surfaces render — routes,\n * navigation and page copy, the settable attributes, the import/export\n * envelope, endpoint paths, the featured and responder id lists — is served\n * from here.\n * The published interface manifest supplies it when the pinned\n * `@openhands/extensions` ships one and it passes admission; otherwise the\n * host's own defaults do, which reproduce today's behavior exactly. Copy\n * defaults are null, meaning \"render the host's own translation\".\n */\n\nimport { AUTOMATION_CATALOG } from \"@openhands/extensions/automations\";\nimport { AUTOMATION_TIMEOUT_MAX_SECONDS } from \"#/utils/automation-timeout\";\nimport { validateInterfaceManifest } from \"./interface-validation\";\nimport { AUTOMATION_INTERFACE_CANDIDATE } from \"./manifest-sources\";\nimport type {\n AutomationAttributeName,\n InterfaceEndpointName,\n InterfaceImportExport,\n InterfaceManifest,\n InterfaceRoutes,\n} from \"./types\";\n\n/** The routes this host has registrations for, in `src/routes.ts`. */\nconst MOUNTED_ROUTES: InterfaceRoutes = {\n list: \"/automations\",\n setup: \"/automations/new/:automationId\",\n detail: \"/automations/:automationId\",\n};\n\nconst DEFAULT_ENDPOINTS: InterfaceManifest[\"endpoints\"] = {\n list: \"/v1\",\n detail: \"/v1/{id}\",\n dispatch: \"/v1/{id}/dispatch\",\n runs: \"/v1/{id}/runs\",\n tarball: \"/v1/{id}/tarball\",\n health: \"/health\",\n capabilities: \"/v1/capabilities\",\n validate: \"/v1/validate\",\n createPrompt: \"/v1/preset/prompt\",\n createPlugin: \"/v1/preset/plugin\",\n};\n\nconst DEFAULT_IMPORT_EXPORT: InterfaceImportExport = {\n fileKind: \"automation\",\n fileVersion: 1,\n filenameSuffix: \".automation.json\",\n importDefaults: {\n repoProvider: \"github\",\n placeholderEventSource: \"agent-canvas-import\",\n },\n};\n\nconst DEFAULT_DOCS_URL =\n \"https://docs.openhands.dev/openhands/usage/automations/overview\";\n\n/**\n * Proven automations featured above the Beta group. NOT derived from\n * popularityRank (slack-standup-digest@94 outranks slack-channel-monitor@92\n * yet is Beta).\n */\nconst DEFAULT_FEATURED_AUTOMATION_IDS: readonly string[] = [\n \"github-pr-reviewer\",\n \"github-repo-monitor\",\n \"slack-channel-monitor\",\n];\n\n/**\n * Integrations whose automations are treated as event responders that poll\n * continuously, and so get the deployment-choice dialog.\n */\nconst DEFAULT_RESPONDER_INTEGRATION_IDS: readonly string[] = [\n \"github\",\n \"slack\",\n];\n\nexport interface AttributeSpec {\n present: boolean;\n /** Null means \"render the host's own translation\". */\n label: string | null;\n help: string | null;\n required: boolean;\n min: number | null;\n max: number | null;\n}\n\nconst ABSENT_ATTRIBUTE: AttributeSpec = {\n present: false,\n label: null,\n help: null,\n required: false,\n min: null,\n max: null,\n};\n\n/** Today's edit dialog, restated as specs so absence of a manifest changes nothing. */\nconst DEFAULT_ATTRIBUTES: Record<AutomationAttributeName, AttributeSpec> = {\n name: {\n present: true,\n label: null,\n help: null,\n required: true,\n min: null,\n max: null,\n },\n prompt: {\n present: true,\n label: null,\n help: null,\n required: false,\n min: null,\n max: null,\n },\n model: {\n present: true,\n label: null,\n help: null,\n required: false,\n min: null,\n max: null,\n },\n timeout: {\n present: true,\n label: null,\n help: null,\n required: false,\n min: 1,\n max: AUTOMATION_TIMEOUT_MAX_SECONDS,\n },\n schedule: {\n present: true,\n label: null,\n help: null,\n required: false,\n min: null,\n max: null,\n },\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction getCatalogIds(): ReadonlySet<string> {\n return new Set(\n (AUTOMATION_CATALOG as readonly unknown[]).flatMap((entry) =>\n isRecord(entry) && typeof entry.id === \"string\" ? [entry.id] : [],\n ),\n );\n}\n\nfunction admitInterfaceManifest(candidate: unknown): InterfaceManifest | null {\n if (candidate === undefined) return null;\n\n const result = validateInterfaceManifest(candidate, {\n catalogIds: getCatalogIds(),\n mountedRoutes: MOUNTED_ROUTES,\n });\n if (!result.valid) {\n // Mirrors the setup registry: a rejected manifest is skipped loudly, and\n // the host's defaults stand.\n console.warn(\n \"Rejected the automation interface manifest:\",\n result.errors.join(\"; \"),\n );\n return null;\n }\n return candidate as InterfaceManifest;\n}\n\nconst ADMITTED = admitInterfaceManifest(AUTOMATION_INTERFACE_CANDIDATE);\n\nfunction substituteRouteParam(pattern: string, id: string): string {\n return pattern.replace(\":automationId\", encodeURIComponent(id));\n}\n\nexport function automationListPath(): string {\n return (ADMITTED?.routes ?? MOUNTED_ROUTES).list;\n}\n\nexport function automationSetupPath(id: string): string {\n return substituteRouteParam((ADMITTED?.routes ?? MOUNTED_ROUTES).setup, id);\n}\n\nexport function automationDetailPath(id: string): string {\n return substituteRouteParam((ADMITTED?.routes ?? MOUNTED_ROUTES).detail, id);\n}\n\nexport function getAutomationEndpoint(name: InterfaceEndpointName): string {\n return (ADMITTED?.endpoints ?? DEFAULT_ENDPOINTS)[name];\n}\n\n/** An id-parameterized endpoint with `{id}` substituted, encoded. */\nexport function getAutomationIdEndpoint(\n name: \"detail\" | \"dispatch\" | \"runs\" | \"tarball\",\n id: string,\n): string {\n return getAutomationEndpoint(name).replace(\"{id}\", encodeURIComponent(id));\n}\n\nexport interface InterfaceCopy {\n sidebarLabel: string | null;\n commandMenuTitle: string | null;\n commandMenuDescription: string | null;\n commandMenuKeywords: string | null;\n listTitle: string | null;\n listSubtitle: string | null;\n detailBackLabel: string | null;\n editTitle: string | null;\n}\n\nconst HOST_COPY: InterfaceCopy = {\n sidebarLabel: null,\n commandMenuTitle: null,\n commandMenuDescription: null,\n commandMenuKeywords: null,\n listTitle: null,\n listSubtitle: null,\n detailBackLabel: null,\n editTitle: null,\n};\n\nexport function getInterfaceCopy(): InterfaceCopy {\n if (!ADMITTED) return HOST_COPY;\n return {\n sidebarLabel: ADMITTED.navigation.sidebar.label,\n commandMenuTitle: ADMITTED.navigation.commandMenu.title,\n commandMenuDescription: ADMITTED.navigation.commandMenu.description,\n commandMenuKeywords: ADMITTED.navigation.commandMenu.keywords,\n listTitle: ADMITTED.pages.list.title,\n listSubtitle: ADMITTED.pages.list.subtitle,\n detailBackLabel: ADMITTED.pages.detail.backLabel,\n editTitle: ADMITTED.pages.edit.title,\n };\n}\n\nexport function getAttributeSpec(name: AutomationAttributeName): AttributeSpec {\n if (!ADMITTED) return DEFAULT_ATTRIBUTES[name];\n\n const attribute = ADMITTED.attributes[name];\n if (!attribute) return ABSENT_ATTRIBUTE;\n return {\n present: true,\n label: attribute.label,\n help: attribute.help ?? null,\n required: attribute.required,\n min: attribute.constraints?.min ?? null,\n max: attribute.constraints?.max ?? null,\n };\n}\n\nexport function getImportExportSpec(): InterfaceImportExport {\n return ADMITTED?.importExport ?? DEFAULT_IMPORT_EXPORT;\n}\n\nexport function getAutomationsDocsUrl(): string {\n return ADMITTED?.docsUrl ?? DEFAULT_DOCS_URL;\n}\n\nexport function getFeaturedAutomationIds(): readonly string[] {\n return ADMITTED?.featuredAutomationIds ?? DEFAULT_FEATURED_AUTOMATION_IDS;\n}\n\nexport function getResponderIntegrationIds(): readonly string[] {\n return ADMITTED?.responderIntegrationIds ?? DEFAULT_RESPONDER_INTEGRATION_IDS;\n}\n"],"mappings":";;;;AA0BA,IAAM,IAAkC;CACtC,MAAM;CACN,OAAO;CACP,QAAQ;AACV,GAEM,IAAoD;CACxD,MAAM;CACN,QAAQ;CACR,UAAU;CACV,MAAM;CACN,SAAS;CACT,QAAQ;CACR,cAAc;CACd,UAAU;CACV,cAAc;CACd,cAAc;AAChB,GAEM,IAA+C;CACnD,UAAU;CACV,aAAa;CACb,gBAAgB;CAChB,gBAAgB;EACd,cAAc;EACd,wBAAwB;CAC1B;AACF;AAwFA,SAAS,EAAS,GAAkD;CAClE,OAAO,OAAO,KAAU,cAAY,KAAkB,CAAC,MAAM,QAAQ,CAAK;AAC5E;AAEA,SAAS,IAAqC;CAC5C,OAAO,IAAI,IACR,EAA0C,SAAS,MAClD,EAAS,CAAK,KAAK,OAAO,EAAM,MAAO,WAAW,CAAC,EAAM,EAAE,IAAI,CAAC,CAClE,CACF;AACF;AAEA,SAAS,EAAuB,GAA8C;CAC5E,IAAI,MAAc,KAAA,GAAW,OAAO;CAEpC,IAAM,IAAS,EAA0B,GAAW;EAClD,YAAY,EAAc;EAC1B,eAAe;CACjB,CAAC;CAUD,OATK,EAAO,QASL,KANL,QAAQ,KACN,+CACA,EAAO,OAAO,KAAK,IAAI,CACzB,GACO;AAGX;AAEA,IAAM,IAAW,EAAuB,CAA8B;AAMtE,SAAgB,IAA6B;CAC3C,QAAQ,GAAU,UAAU,GAAgB;AAC9C;AAUA,SAAgB,EAAsB,GAAqC;CACzE,QAAQ,GAAU,aAAa,GAAmB;AACpD;AAGA,SAAgB,EACd,GACA,GACQ;CACR,OAAO,EAAsB,CAAI,EAAE,QAAQ,QAAQ,mBAAmB,CAAE,CAAC;AAC3E;AAaA,IAAM,IAA2B;CAC/B,cAAc;CACd,kBAAkB;CAClB,wBAAwB;CACxB,qBAAqB;CACrB,WAAW;CACX,cAAc;CACd,iBAAiB;CACjB,WAAW;AACb;AAEA,SAAgB,IAAkC;CAEhD,OADK,IACE;EACL,cAAc,EAAS,WAAW,QAAQ;EAC1C,kBAAkB,EAAS,WAAW,YAAY;EAClD,wBAAwB,EAAS,WAAW,YAAY;EACxD,qBAAqB,EAAS,WAAW,YAAY;EACrD,WAAW,EAAS,MAAM,KAAK;EAC/B,cAAc,EAAS,MAAM,KAAK;EAClC,iBAAiB,EAAS,MAAM,OAAO;EACvC,WAAW,EAAS,MAAM,KAAK;CACjC,IAVsB;AAWxB;AAiBA,SAAgB,IAA6C;CAC3D,OAAO,GAAU,gBAAgB;AACnC"}
@@ -0,0 +1,2 @@
1
+ require("../_virtual/_rolldown/runtime.cjs"),require("../utils/automation-catalog.cjs");var e=require("./automation-interface.cjs").getAutomationEndpoint(`createPrompt`);exports.AUTOMATION_CREATE_ENDPOINT=e;
2
+ //# sourceMappingURL=automation-setup.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"automation-setup.cjs","names":[],"sources":["../../src/manifests/automation-setup.ts"],"sourcesContent":["/**\n * The only module that knows a setup entry configures an *automation*.\n *\n * The published contract states just what varies between entries; everything\n * derivable is the host's to generate. That derivation is here, and nowhere\n * else, so the rest of `src/manifests/` stays about forms rather than about\n * automations.\n *\n * The algorithm mirrors `tests/test_automation_setup.py::_render_payload` in\n * `OpenHands/extensions`, which is the authoritative reference: it produces the\n * request bodies published in that repository's contract fixtures, and those\n * were verified against the live service. The create model is `extra=\"forbid\"`,\n * so any divergence is a hard 422 rather than a dropped field.\n */\n\nimport { findAutomationCommand } from \"#/utils/automation-catalog\";\nimport { getAutomationEndpoint } from \"./automation-interface\";\nimport { collectFields } from \"./manifest-local-validation\";\nimport { interpolateText } from \"./manifest-template\";\nimport type {\n SetupBlock,\n SetupEntry,\n SetupFormValues,\n SetupRequestBody,\n SetupTriggerKind,\n} from \"./types\";\n\n/** The creation endpoint a derived draft would be posted to. */\nexport const AUTOMATION_CREATE_ENDPOINT = getAutomationEndpoint(\"createPrompt\");\n\n/**\n * Trigger properties a form field may fill, per trigger kind. A field under a\n * trigger kind whose name is listed here fills the trigger property of the same\n * name; anything else there, such as a phrase to match, is an input to `filter`.\n */\nconst TRIGGER_PROPERTIES: Record<SetupTriggerKind, readonly string[]> = {\n cron: [\"schedule\", \"timezone\"],\n event: [\"on\"],\n};\n\n/**\n * Repository properties a form field may fill, read the same way\n * {@link TRIGGER_PROPERTIES} is: a field whose name is listed here fills the\n * `repos[]` property of the same name. `url` and `provider` are absent because\n * neither is answered by a field of its own — they come from the repo-picker's\n * value and from its declared provider.\n *\n * These two lists are the only field names the derivation knows; everything\n * else it reads it finds by field type. An entry that wants a base branch in\n * its request therefore has to name that field `ref`.\n */\nconst REPO_PROPERTIES: readonly string[] = [\"ref\"];\n\nconst FORM_PLACEHOLDER_PATTERN = /\\{\\{form\\.([A-Za-z0-9_.]+)\\}\\}/g;\n\n/** The repository input, which supplies `repos` and an event trigger's source. */\nfunction findRepoPickerField(setup: SetupBlock) {\n const match = Object.entries(collectFields(setup)).find(\n ([, field]) => field.type === \"repo-picker\",\n );\n return match ? { name: match[0], field: match[1] } : null;\n}\n\n/** The single trigger kind a direct entry declares, with its fields. */\nfunction getTrigger(setup: SetupBlock) {\n const entries = Object.entries(setup.form.triggers ?? {});\n if (entries.length === 0) return null;\n const [kind, fields] = entries[0];\n return { kind: kind as SetupTriggerKind, fields: fields ?? {} };\n}\n\n/**\n * The create request body these form values produce.\n *\n * No entry declares it: `name` comes from the entry, `repos` from the\n * repo-picker field and its declared provider, and `trigger` from the key and\n * fields under `form.triggers`. Only `prompt` and an event `filter` are\n * declared, because only they cannot be read off the form.\n *\n * Returns null for an assisted entry, which hands setup to a conversation\n * instead of sending a body.\n */\nexport function buildCreatePayload(\n entry: SetupEntry,\n values: SetupFormValues,\n): SetupRequestBody | null {\n const { setup } = entry;\n if (setup.mode !== \"direct\" || !setup.prompt) return null;\n\n const scope = { form: values, automation: entry };\n const repoPicker = findRepoPickerField(setup);\n const repository = repoPicker ? values[repoPicker.name] : undefined;\n\n const payload: SetupRequestBody = {\n name: repository ? `${entry.name} - ${repository}` : entry.name,\n prompt: interpolateText(setup.prompt, scope),\n };\n\n if (repository && repoPicker?.field.provider) {\n const declared = REPO_PROPERTIES.filter((name) => name in values);\n payload.repos = [\n {\n url: repository,\n ...Object.fromEntries(declared.map((name) => [name, values[name]])),\n provider: repoPicker.field.provider,\n },\n ];\n }\n\n const trigger = getTrigger(setup);\n if (trigger) {\n const properties = TRIGGER_PROPERTIES[trigger.kind];\n const declared = Object.keys(trigger.fields).filter((name) =>\n properties.includes(name),\n );\n\n payload.trigger = {\n type: trigger.kind,\n ...Object.fromEntries(declared.map((name) => [name, values[name] ?? \"\"])),\n ...(trigger.kind === \"event\" && {\n source: repoPicker?.field.provider ?? \"\",\n // A filter is optional: an entry that declares none accepts every\n // delivered event, so the key is left off rather than sent empty.\n ...(setup.filter && { filter: interpolateText(setup.filter, scope) }),\n }),\n };\n }\n\n return payload;\n}\n\n/**\n * The preflight body the host sends. The same shape for every entry, so no\n * entry declares it.\n */\nexport function buildPreflightBody(\n entry: SetupEntry,\n values: SetupFormValues,\n): SetupRequestBody | null {\n const draft = buildCreatePayload(entry, values);\n if (!draft) return null;\n\n return {\n automationId: entry.id,\n endpoint: AUTOMATION_CREATE_ENDPOINT,\n draft,\n };\n}\n\n/**\n * What an assisted entry sends into the conversation that finishes setup.\n *\n * The command is not declared either: it lives once, in the owning skill's own\n * `triggers` frontmatter, and the skill defaults to the entry's id. An entry\n * whose skill is invoked by description rather than by command contributes\n * nothing here, and the answers alone open the conversation.\n */\nexport function buildAssistedMessage(\n entry: SetupEntry,\n values: SetupFormValues,\n): string {\n const command = findAutomationCommand(entry);\n const message = entry.setup.message\n ? interpolateText(entry.setup.message, { form: values, automation: entry })\n : \"\";\n\n return [command, message].filter(Boolean).join(\"\\n\\n\");\n}\n\nfunction collectPlaceholderFields(value: string): string[] {\n const names = Array.from(\n value.matchAll(FORM_PLACEHOLDER_PATTERN),\n (match) => match[1],\n );\n return Array.from(new Set(names));\n}\n\n/**\n * Which form fields built each payload path.\n *\n * Preflight and the create endpoint reject a draft by payload path, and the\n * host has to turn that back into a highlighted input. Building the body with\n * each field standing in for its own value recovers the mapping exactly, so an\n * entry does not declare it.\n */\nexport function deriveErrorMap(entry: SetupEntry): Record<string, string[]> {\n const mapping: Record<string, string[]> = {};\n\n const standIns = Object.fromEntries(\n Object.keys(collectFields(entry.setup)).map((name) => [\n name,\n `{{form.${name}}}`,\n ]),\n );\n const template = buildCreatePayload(entry, standIns);\n if (!template) return mapping;\n\n const walk = (node: unknown, path: string): void => {\n if (Array.isArray(node)) {\n node.forEach((item, index) => walk(item, `${path}[${index}]`));\n return;\n }\n if (typeof node === \"object\" && node !== null) {\n Object.entries(node).forEach(([key, item]) =>\n walk(item, path ? `${path}.${key}` : key),\n );\n return;\n }\n if (typeof node === \"string\") {\n const names = collectPlaceholderFields(node);\n if (names.length > 0) mapping[path] = names;\n }\n };\n\n walk(template, \"\");\n return mapping;\n}\n"],"mappings":"wFA4BA,IAAa,sCAA6B,EAAA,sBAAsB,cAAc"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The only module that knows a setup entry configures an *automation*.
3
+ *
4
+ * The published contract states just what varies between entries; everything
5
+ * derivable is the host's to generate. That derivation is here, and nowhere
6
+ * else, so the rest of `src/manifests/` stays about forms rather than about
7
+ * automations.
8
+ *
9
+ * The algorithm mirrors `tests/test_automation_setup.py::_render_payload` in
10
+ * `OpenHands/extensions`, which is the authoritative reference: it produces the
11
+ * request bodies published in that repository's contract fixtures, and those
12
+ * were verified against the live service. The create model is `extra="forbid"`,
13
+ * so any divergence is a hard 422 rather than a dropped field.
14
+ */
15
+ import type { SetupEntry, SetupFormValues, SetupRequestBody } from "./types";
16
+ /** The creation endpoint a derived draft would be posted to. */
17
+ export declare const AUTOMATION_CREATE_ENDPOINT: string;
18
+ /**
19
+ * The create request body these form values produce.
20
+ *
21
+ * No entry declares it: `name` comes from the entry, `repos` from the
22
+ * repo-picker field and its declared provider, and `trigger` from the key and
23
+ * fields under `form.triggers`. Only `prompt` and an event `filter` are
24
+ * declared, because only they cannot be read off the form.
25
+ *
26
+ * Returns null for an assisted entry, which hands setup to a conversation
27
+ * instead of sending a body.
28
+ */
29
+ export declare function buildCreatePayload(entry: SetupEntry, values: SetupFormValues): SetupRequestBody | null;
30
+ /**
31
+ * The preflight body the host sends. The same shape for every entry, so no
32
+ * entry declares it.
33
+ */
34
+ export declare function buildPreflightBody(entry: SetupEntry, values: SetupFormValues): SetupRequestBody | null;
35
+ /**
36
+ * What an assisted entry sends into the conversation that finishes setup.
37
+ *
38
+ * The command is not declared either: it lives once, in the owning skill's own
39
+ * `triggers` frontmatter, and the skill defaults to the entry's id. An entry
40
+ * whose skill is invoked by description rather than by command contributes
41
+ * nothing here, and the answers alone open the conversation.
42
+ */
43
+ export declare function buildAssistedMessage(entry: SetupEntry, values: SetupFormValues): string;
44
+ /**
45
+ * Which form fields built each payload path.
46
+ *
47
+ * Preflight and the create endpoint reject a draft by payload path, and the
48
+ * host has to turn that back into a highlighted input. Building the body with
49
+ * each field standing in for its own value recovers the mapping exactly, so an
50
+ * entry does not declare it.
51
+ */
52
+ export declare function deriveErrorMap(entry: SetupEntry): Record<string, string[]>;
@@ -0,0 +1,8 @@
1
+ import "../utils/automation-catalog.js";
2
+ import { getAutomationEndpoint as e } from "./automation-interface.js";
3
+ //#region src/manifests/automation-setup.ts
4
+ var t = e("createPrompt");
5
+ //#endregion
6
+ export { t as AUTOMATION_CREATE_ENDPOINT };
7
+
8
+ //# sourceMappingURL=automation-setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"automation-setup.js","names":[],"sources":["../../src/manifests/automation-setup.ts"],"sourcesContent":["/**\n * The only module that knows a setup entry configures an *automation*.\n *\n * The published contract states just what varies between entries; everything\n * derivable is the host's to generate. That derivation is here, and nowhere\n * else, so the rest of `src/manifests/` stays about forms rather than about\n * automations.\n *\n * The algorithm mirrors `tests/test_automation_setup.py::_render_payload` in\n * `OpenHands/extensions`, which is the authoritative reference: it produces the\n * request bodies published in that repository's contract fixtures, and those\n * were verified against the live service. The create model is `extra=\"forbid\"`,\n * so any divergence is a hard 422 rather than a dropped field.\n */\n\nimport { findAutomationCommand } from \"#/utils/automation-catalog\";\nimport { getAutomationEndpoint } from \"./automation-interface\";\nimport { collectFields } from \"./manifest-local-validation\";\nimport { interpolateText } from \"./manifest-template\";\nimport type {\n SetupBlock,\n SetupEntry,\n SetupFormValues,\n SetupRequestBody,\n SetupTriggerKind,\n} from \"./types\";\n\n/** The creation endpoint a derived draft would be posted to. */\nexport const AUTOMATION_CREATE_ENDPOINT = getAutomationEndpoint(\"createPrompt\");\n\n/**\n * Trigger properties a form field may fill, per trigger kind. A field under a\n * trigger kind whose name is listed here fills the trigger property of the same\n * name; anything else there, such as a phrase to match, is an input to `filter`.\n */\nconst TRIGGER_PROPERTIES: Record<SetupTriggerKind, readonly string[]> = {\n cron: [\"schedule\", \"timezone\"],\n event: [\"on\"],\n};\n\n/**\n * Repository properties a form field may fill, read the same way\n * {@link TRIGGER_PROPERTIES} is: a field whose name is listed here fills the\n * `repos[]` property of the same name. `url` and `provider` are absent because\n * neither is answered by a field of its own — they come from the repo-picker's\n * value and from its declared provider.\n *\n * These two lists are the only field names the derivation knows; everything\n * else it reads it finds by field type. An entry that wants a base branch in\n * its request therefore has to name that field `ref`.\n */\nconst REPO_PROPERTIES: readonly string[] = [\"ref\"];\n\nconst FORM_PLACEHOLDER_PATTERN = /\\{\\{form\\.([A-Za-z0-9_.]+)\\}\\}/g;\n\n/** The repository input, which supplies `repos` and an event trigger's source. */\nfunction findRepoPickerField(setup: SetupBlock) {\n const match = Object.entries(collectFields(setup)).find(\n ([, field]) => field.type === \"repo-picker\",\n );\n return match ? { name: match[0], field: match[1] } : null;\n}\n\n/** The single trigger kind a direct entry declares, with its fields. */\nfunction getTrigger(setup: SetupBlock) {\n const entries = Object.entries(setup.form.triggers ?? {});\n if (entries.length === 0) return null;\n const [kind, fields] = entries[0];\n return { kind: kind as SetupTriggerKind, fields: fields ?? {} };\n}\n\n/**\n * The create request body these form values produce.\n *\n * No entry declares it: `name` comes from the entry, `repos` from the\n * repo-picker field and its declared provider, and `trigger` from the key and\n * fields under `form.triggers`. Only `prompt` and an event `filter` are\n * declared, because only they cannot be read off the form.\n *\n * Returns null for an assisted entry, which hands setup to a conversation\n * instead of sending a body.\n */\nexport function buildCreatePayload(\n entry: SetupEntry,\n values: SetupFormValues,\n): SetupRequestBody | null {\n const { setup } = entry;\n if (setup.mode !== \"direct\" || !setup.prompt) return null;\n\n const scope = { form: values, automation: entry };\n const repoPicker = findRepoPickerField(setup);\n const repository = repoPicker ? values[repoPicker.name] : undefined;\n\n const payload: SetupRequestBody = {\n name: repository ? `${entry.name} - ${repository}` : entry.name,\n prompt: interpolateText(setup.prompt, scope),\n };\n\n if (repository && repoPicker?.field.provider) {\n const declared = REPO_PROPERTIES.filter((name) => name in values);\n payload.repos = [\n {\n url: repository,\n ...Object.fromEntries(declared.map((name) => [name, values[name]])),\n provider: repoPicker.field.provider,\n },\n ];\n }\n\n const trigger = getTrigger(setup);\n if (trigger) {\n const properties = TRIGGER_PROPERTIES[trigger.kind];\n const declared = Object.keys(trigger.fields).filter((name) =>\n properties.includes(name),\n );\n\n payload.trigger = {\n type: trigger.kind,\n ...Object.fromEntries(declared.map((name) => [name, values[name] ?? \"\"])),\n ...(trigger.kind === \"event\" && {\n source: repoPicker?.field.provider ?? \"\",\n // A filter is optional: an entry that declares none accepts every\n // delivered event, so the key is left off rather than sent empty.\n ...(setup.filter && { filter: interpolateText(setup.filter, scope) }),\n }),\n };\n }\n\n return payload;\n}\n\n/**\n * The preflight body the host sends. The same shape for every entry, so no\n * entry declares it.\n */\nexport function buildPreflightBody(\n entry: SetupEntry,\n values: SetupFormValues,\n): SetupRequestBody | null {\n const draft = buildCreatePayload(entry, values);\n if (!draft) return null;\n\n return {\n automationId: entry.id,\n endpoint: AUTOMATION_CREATE_ENDPOINT,\n draft,\n };\n}\n\n/**\n * What an assisted entry sends into the conversation that finishes setup.\n *\n * The command is not declared either: it lives once, in the owning skill's own\n * `triggers` frontmatter, and the skill defaults to the entry's id. An entry\n * whose skill is invoked by description rather than by command contributes\n * nothing here, and the answers alone open the conversation.\n */\nexport function buildAssistedMessage(\n entry: SetupEntry,\n values: SetupFormValues,\n): string {\n const command = findAutomationCommand(entry);\n const message = entry.setup.message\n ? interpolateText(entry.setup.message, { form: values, automation: entry })\n : \"\";\n\n return [command, message].filter(Boolean).join(\"\\n\\n\");\n}\n\nfunction collectPlaceholderFields(value: string): string[] {\n const names = Array.from(\n value.matchAll(FORM_PLACEHOLDER_PATTERN),\n (match) => match[1],\n );\n return Array.from(new Set(names));\n}\n\n/**\n * Which form fields built each payload path.\n *\n * Preflight and the create endpoint reject a draft by payload path, and the\n * host has to turn that back into a highlighted input. Building the body with\n * each field standing in for its own value recovers the mapping exactly, so an\n * entry does not declare it.\n */\nexport function deriveErrorMap(entry: SetupEntry): Record<string, string[]> {\n const mapping: Record<string, string[]> = {};\n\n const standIns = Object.fromEntries(\n Object.keys(collectFields(entry.setup)).map((name) => [\n name,\n `{{form.${name}}}`,\n ]),\n );\n const template = buildCreatePayload(entry, standIns);\n if (!template) return mapping;\n\n const walk = (node: unknown, path: string): void => {\n if (Array.isArray(node)) {\n node.forEach((item, index) => walk(item, `${path}[${index}]`));\n return;\n }\n if (typeof node === \"object\" && node !== null) {\n Object.entries(node).forEach(([key, item]) =>\n walk(item, path ? `${path}.${key}` : key),\n );\n return;\n }\n if (typeof node === \"string\") {\n const names = collectPlaceholderFields(node);\n if (names.length > 0) mapping[path] = names;\n }\n };\n\n walk(template, \"\");\n return mapping;\n}\n"],"mappings":";;;AA4BA,IAAa,IAA6B,EAAsB,cAAc"}
@@ -0,0 +1,2 @@
1
+ require("../_virtual/_rolldown/runtime.cjs");var e=/<[A-Za-z/!]/,t=/^[a-z0-9]+(-[a-z0-9]+)*$/,n=`https://docs.openhands.dev/`,r=/^[a-z][a-z-]*$/,i=/^\.[a-z][a-z.]*json$/,a=/^[a-z0-9][a-z0-9.-]*$/,o=/^\/[A-Za-z0-9/{}_-]*$/,s=/^[^{}]*\{id\}[^{}]*$/,c=[`github`,`gitlab`,`bitbucket`],l={name:{type:`text`,required:!0},prompt:{type:`textarea`,required:!1},model:{type:`llm-profile`,required:!1},timeout:{type:`number`,required:!1},schedule:{type:`schedule`,required:!1}},u=Object.keys(l),d=[`list`,`health`,`capabilities`,`validate`,`createPrompt`,`createPlugin`],f=[`detail`,`dispatch`,`runs`,`tarball`];function p(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}var m=class{errors=[];fail(e,t){return this.errors.push(`${e}: ${t}`),!1}copy(t,n){return typeof t!=`string`||t.length===0?this.fail(n,`must be a non-empty string`):e.test(t)?this.fail(n,`must not contain markup`):!0}closed(e,t,n){Object.keys(e).filter(e=>!t.includes(e)).forEach(e=>this.fail(`${n}.${e}`,`is not allowed`))}record(e,t){return p(e)?!0:(this.fail(t,`must be an object`),!1)}};function h(e,t,n){e.record(t,`routes`)&&(e.closed(t,[`list`,`setup`,`detail`],`routes`),[`list`,`setup`,`detail`].forEach(r=>{t[r]!==n[r]&&e.fail(`routes.${r}`,`must be "${n[r]}"`)}))}function g(e,t){if(e.record(t,`navigation`)&&(e.closed(t,[`sidebar`,`commandMenu`],`navigation`),e.record(t.sidebar,`navigation.sidebar`)&&(e.closed(t.sidebar,[`label`],`navigation.sidebar`),e.copy(t.sidebar.label,`navigation.sidebar.label`)),e.record(t.commandMenu,`navigation.commandMenu`))){let n=t.commandMenu;e.closed(n,[`title`,`description`,`keywords`],`navigation.commandMenu`),e.copy(n.title,`navigation.commandMenu.title`),e.copy(n.description,`navigation.commandMenu.description`),e.copy(n.keywords,`navigation.commandMenu.keywords`)}}function _(e,t){e.record(t,`pages`)&&(e.closed(t,[`list`,`detail`,`edit`],`pages`),e.record(t.list,`pages.list`)&&(e.closed(t.list,[`title`,`subtitle`],`pages.list`),e.copy(t.list.title,`pages.list.title`),e.copy(t.list.subtitle,`pages.list.subtitle`)),e.record(t.detail,`pages.detail`)&&(e.closed(t.detail,[`backLabel`],`pages.detail`),e.copy(t.detail.backLabel,`pages.detail.backLabel`)),e.record(t.edit,`pages.edit`)&&(e.closed(t.edit,[`title`],`pages.edit`),e.copy(t.edit.title,`pages.edit.title`)))}function v(e,t,n,r){if(!e.record(n,r))return;e.closed(n,[`type`,`label`,`help`,`required`,`constraints`],r);let{type:i,label:a,help:o,required:s,constraints:c}=n;if(i!==t.type&&e.fail(`${r}.type`,`must be "${t.type}", the control this host renders`),e.copy(a,`${r}.label`),o!==void 0&&e.copy(o,`${r}.help`),s!==t.required&&e.fail(`${r}.required`,`must be ${t.required}, what this host enforces`),c!==void 0){if(t.type!==`number`)e.fail(`${r}.constraints`,`is only allowed on a number attribute`);else if(e.record(c,`${r}.constraints`)){e.closed(c,[`min`,`max`],`${r}.constraints`);let{min:t,max:n}=c;t!==void 0&&t!==1&&e.fail(`${r}.constraints.min`,`must be the 1 this host enforces`),n!==void 0&&(!Number.isInteger(n)||n<1)&&e.fail(`${r}.constraints.max`,`must be a positive integer`)}}}function y(e,t){if(!e.record(t,`attributes`))return;let n=Object.keys(t);n.length===0&&e.fail(`attributes`,`must declare at least one attribute`),n.forEach(n=>{if(!u.includes(n)){e.fail(`attributes.${n}`,`is not a settable attribute`);return}v(e,l[n],t[n],`attributes.${n}`)})}function b(e,t){if(!e.record(t,`importExport`))return;e.closed(t,[`fileKind`,`fileVersion`,`filenameSuffix`,`importDefaults`],`importExport`);let{fileKind:n,fileVersion:o,filenameSuffix:s,importDefaults:l}=t;(typeof n!=`string`||!r.test(n))&&e.fail(`importExport.fileKind`,`must be a lowercase slug`),o!==1&&e.fail(`importExport.fileVersion`,`must be 1`),(typeof s!=`string`||!i.test(s))&&e.fail(`importExport.filenameSuffix`,`must be a .json suffix`),e.record(l,`importExport.importDefaults`)&&(e.closed(l,[`repoProvider`,`placeholderEventSource`],`importExport.importDefaults`),(typeof l.repoProvider!=`string`||!c.includes(l.repoProvider))&&e.fail(`importExport.importDefaults.repoProvider`,`is not a supported provider`),(typeof l.placeholderEventSource!=`string`||!a.test(l.placeholderEventSource))&&e.fail(`importExport.importDefaults.placeholderEventSource`,`must be a lowercase event source`))}function x(e,t){if(!e.record(t,`endpoints`))return;let n=[...d,...f];e.closed(t,n,`endpoints`);let r=n=>{let r=t[n];return typeof r!=`string`||!o.test(r)||r.includes(`//`)?(e.fail(`endpoints.${n}`,`must be a rooted service-relative path`),null):r};d.forEach(t=>{let n=r(t);n!==null&&/[{}]/.test(n)&&e.fail(`endpoints.${t}`,`must not carry a substitution`)}),f.forEach(t=>{let n=r(t);n!==null&&!s.test(n)&&e.fail(`endpoints.${t}`,`must carry exactly one {id}`)})}function S(e,n,r){if(!Array.isArray(n)||n.length===0)return e.fail(r,`must be a non-empty array`),[];let i=n.filter(n=>typeof n==`string`&&t.test(n)?!0:(e.fail(r,`must contain only lowercase slugs`),!1));return new Set(i).size!==i.length&&e.fail(r,`must not repeat an id`),i}var C=[`version`,`routes`,`navigation`,`pages`,`docsUrl`,`attributes`,`importExport`,`endpoints`,`featuredAutomationIds`,`responderIntegrationIds`];function w(e,t){if(!p(e))return{valid:!1,errors:[`interface: must be an object`]};if(e.version!==`1.0`)return{valid:!1,errors:[`interface.version: must be "1.0"`]};let r=new m;return r.closed(e,C,`interface`),h(r,e.routes,t.mountedRoutes),g(r,e.navigation),_(r,e.pages),(typeof e.docsUrl!=`string`||!e.docsUrl.startsWith(n))&&r.fail(`docsUrl`,`must start with ${n}`),y(r,e.attributes),b(r,e.importExport),x(r,e.endpoints),S(r,e.featuredAutomationIds,`featuredAutomationIds`).forEach(e=>{t.catalogIds.has(e)||r.fail(`featuredAutomationIds`,`${e} is not a catalog entry`)}),S(r,e.responderIntegrationIds,`responderIntegrationIds`),{valid:r.errors.length===0,errors:r.errors}}exports.validateInterfaceManifest=w;
2
+ //# sourceMappingURL=interface-validation.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interface-validation.cjs","names":[],"sources":["../../src/manifests/interface-validation.ts"],"sourcesContent":["/**\n * Admission policy for the extension-published Automation interface manifest.\n *\n * Like a setup entry, the interface manifest is data authored in a different\n * repository that instructs this host to render copy, build links, and address\n * requests, so the host decides what it is *permitted* to state. Admission is\n * all-or-nothing: one bad field rejects the whole manifest, and the host falls\n * back to its own defaults rather than rendering a partially-trusted mix.\n */\n\nimport type {\n AutomationAttributeName,\n InterfaceAttributeType,\n InterfaceRoutes,\n} from \"./types\";\nimport { INTERFACE_VERSION } from \"./types\";\n\n/** Copy must never be able to inject markup into the host. */\nconst MARKUP_PATTERN = /<[A-Za-z/!]/;\nconst SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;\n/** The one URL the manifest may state, pinned to the product documentation. */\nconst DOCS_URL_PREFIX = \"https://docs.openhands.dev/\";\nconst FILE_KIND_PATTERN = /^[a-z][a-z-]*$/;\nconst FILENAME_SUFFIX_PATTERN = /^\\.[a-z][a-z.]*json$/;\nconst EVENT_SOURCE_PATTERN = /^[a-z0-9][a-z0-9.-]*$/;\n/** Service-relative only: rooted, no traversal, no query, no fragment. */\nconst ENDPOINT_PATTERN = /^\\/[A-Za-z0-9/{}_-]*$/;\n/** Exactly one `{id}` substitution and no other braces. */\nconst ID_ENDPOINT_PATTERN = /^[^{}]*\\{id\\}[^{}]*$/;\n\nconst GIT_PROVIDERS = [\"github\", \"gitlab\", \"bitbucket\"] as const;\n/**\n * The attribute semantics this host's edit dialog implements: the control it\n * renders per property and the requiredness it enforces. Admission pins a\n * declared attribute to exactly these, so an admitted manifest can never\n * promise a control, a requiredness, or (below) a minimum the form would\n * silently ignore.\n */\nconst HOST_ATTRIBUTES: Record<\n AutomationAttributeName,\n { type: InterfaceAttributeType; required: boolean }\n> = {\n name: { type: \"text\", required: true },\n prompt: { type: \"textarea\", required: false },\n model: { type: \"llm-profile\", required: false },\n timeout: { type: \"number\", required: false },\n schedule: { type: \"schedule\", required: false },\n};\nconst ATTRIBUTE_NAMES = Object.keys(HOST_ATTRIBUTES);\nconst PLAIN_ENDPOINT_NAMES = [\n \"list\",\n \"health\",\n \"capabilities\",\n \"validate\",\n \"createPrompt\",\n \"createPlugin\",\n] as const;\nconst ID_ENDPOINT_NAMES = [\"detail\", \"dispatch\", \"runs\", \"tarball\"] as const;\n\nexport interface InterfaceValidationContext {\n /** Ids of the published automation catalog, for the featured-list check. */\n catalogIds: ReadonlySet<string>;\n /** The routes this host has registrations for. A manifest must match them. */\n mountedRoutes: InterfaceRoutes;\n}\n\nexport interface InterfaceValidationResult {\n valid: boolean;\n errors: string[];\n}\n\ntype Rec = Record<string, unknown>;\n\nfunction isRecord(value: unknown): value is Rec {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nclass InterfaceChecker {\n readonly errors: string[] = [];\n\n fail(path: string, reason: string): false {\n this.errors.push(`${path}: ${reason}`);\n return false;\n }\n\n /** Literal user-visible copy. Carries no markup. */\n copy(value: unknown, path: string): boolean {\n if (typeof value !== \"string\" || value.length === 0) {\n return this.fail(path, \"must be a non-empty string\");\n }\n if (MARKUP_PATTERN.test(value)) {\n return this.fail(path, \"must not contain markup\");\n }\n return true;\n }\n\n /** A closed object: every present key must be an expected one. */\n closed(container: Rec, allowed: readonly string[], path: string): void {\n Object.keys(container)\n .filter((key) => !allowed.includes(key))\n .forEach((key) => this.fail(`${path}.${key}`, \"is not allowed\"));\n }\n\n record(value: unknown, path: string): value is Rec {\n if (isRecord(value)) return true;\n this.fail(path, \"must be an object\");\n return false;\n }\n}\n\nfunction checkRoutes(\n check: InterfaceChecker,\n routes: unknown,\n mounted: InterfaceRoutes,\n): void {\n if (!check.record(routes, \"routes\")) return;\n check.closed(routes, [\"list\", \"setup\", \"detail\"], \"routes\");\n\n // The host serves what it has registrations for, so a declared route must be\n // exactly the mounted shape — the manifest owns link construction, not the\n // router table.\n ([\"list\", \"setup\", \"detail\"] as const).forEach((name) => {\n if (routes[name] !== mounted[name]) {\n check.fail(`routes.${name}`, `must be \"${mounted[name]}\"`);\n }\n });\n}\n\nfunction checkNavigation(check: InterfaceChecker, navigation: unknown): void {\n if (!check.record(navigation, \"navigation\")) return;\n check.closed(navigation, [\"sidebar\", \"commandMenu\"], \"navigation\");\n\n if (check.record(navigation.sidebar, \"navigation.sidebar\")) {\n check.closed(navigation.sidebar, [\"label\"], \"navigation.sidebar\");\n check.copy(navigation.sidebar.label, \"navigation.sidebar.label\");\n }\n if (check.record(navigation.commandMenu, \"navigation.commandMenu\")) {\n const menu = navigation.commandMenu;\n check.closed(\n menu,\n [\"title\", \"description\", \"keywords\"],\n \"navigation.commandMenu\",\n );\n check.copy(menu.title, \"navigation.commandMenu.title\");\n check.copy(menu.description, \"navigation.commandMenu.description\");\n check.copy(menu.keywords, \"navigation.commandMenu.keywords\");\n }\n}\n\nfunction checkPages(check: InterfaceChecker, pages: unknown): void {\n if (!check.record(pages, \"pages\")) return;\n check.closed(pages, [\"list\", \"detail\", \"edit\"], \"pages\");\n\n if (check.record(pages.list, \"pages.list\")) {\n check.closed(pages.list, [\"title\", \"subtitle\"], \"pages.list\");\n check.copy(pages.list.title, \"pages.list.title\");\n check.copy(pages.list.subtitle, \"pages.list.subtitle\");\n }\n if (check.record(pages.detail, \"pages.detail\")) {\n check.closed(pages.detail, [\"backLabel\"], \"pages.detail\");\n check.copy(pages.detail.backLabel, \"pages.detail.backLabel\");\n }\n if (check.record(pages.edit, \"pages.edit\")) {\n check.closed(pages.edit, [\"title\"], \"pages.edit\");\n check.copy(pages.edit.title, \"pages.edit.title\");\n }\n}\n\nfunction checkAttribute(\n check: InterfaceChecker,\n host: { type: InterfaceAttributeType; required: boolean },\n attribute: unknown,\n path: string,\n): void {\n if (!check.record(attribute, path)) return;\n check.closed(\n attribute,\n [\"type\", \"label\", \"help\", \"required\", \"constraints\"],\n path,\n );\n\n const { type, label, help, required, constraints } = attribute;\n if (type !== host.type) {\n check.fail(\n `${path}.type`,\n `must be \"${host.type}\", the control this host renders`,\n );\n }\n check.copy(label, `${path}.label`);\n if (help !== undefined) check.copy(help, `${path}.help`);\n if (required !== host.required) {\n check.fail(\n `${path}.required`,\n `must be ${host.required}, what this host enforces`,\n );\n }\n\n if (constraints !== undefined) {\n if (host.type !== \"number\") {\n check.fail(\n `${path}.constraints`,\n \"is only allowed on a number attribute\",\n );\n } else if (check.record(constraints, `${path}.constraints`)) {\n check.closed(constraints, [\"min\", \"max\"], `${path}.constraints`);\n const { min, max } = constraints;\n // The form validates a positive integer, i.e. an effective minimum of 1;\n // any other declared minimum would be a promise it does not keep.\n if (min !== undefined && min !== 1) {\n check.fail(\n `${path}.constraints.min`,\n \"must be the 1 this host enforces\",\n );\n }\n if (\n max !== undefined &&\n (!Number.isInteger(max) || (max as number) < 1)\n ) {\n check.fail(`${path}.constraints.max`, \"must be a positive integer\");\n }\n }\n }\n}\n\nfunction checkAttributes(check: InterfaceChecker, attributes: unknown): void {\n if (!check.record(attributes, \"attributes\")) return;\n const names = Object.keys(attributes);\n if (names.length === 0) {\n check.fail(\"attributes\", \"must declare at least one attribute\");\n }\n names.forEach((name) => {\n if (!ATTRIBUTE_NAMES.includes(name)) {\n check.fail(`attributes.${name}`, \"is not a settable attribute\");\n return;\n }\n checkAttribute(\n check,\n HOST_ATTRIBUTES[name as AutomationAttributeName],\n attributes[name],\n `attributes.${name}`,\n );\n });\n}\n\nfunction checkImportExport(\n check: InterfaceChecker,\n importExport: unknown,\n): void {\n if (!check.record(importExport, \"importExport\")) return;\n check.closed(\n importExport,\n [\"fileKind\", \"fileVersion\", \"filenameSuffix\", \"importDefaults\"],\n \"importExport\",\n );\n\n const { fileKind, fileVersion, filenameSuffix, importDefaults } =\n importExport;\n if (typeof fileKind !== \"string\" || !FILE_KIND_PATTERN.test(fileKind)) {\n check.fail(\"importExport.fileKind\", \"must be a lowercase slug\");\n }\n if (fileVersion !== 1) {\n check.fail(\"importExport.fileVersion\", \"must be 1\");\n }\n if (\n typeof filenameSuffix !== \"string\" ||\n !FILENAME_SUFFIX_PATTERN.test(filenameSuffix)\n ) {\n check.fail(\"importExport.filenameSuffix\", \"must be a .json suffix\");\n }\n if (check.record(importDefaults, \"importExport.importDefaults\")) {\n check.closed(\n importDefaults,\n [\"repoProvider\", \"placeholderEventSource\"],\n \"importExport.importDefaults\",\n );\n if (\n typeof importDefaults.repoProvider !== \"string\" ||\n !(GIT_PROVIDERS as readonly string[]).includes(\n importDefaults.repoProvider,\n )\n ) {\n check.fail(\n \"importExport.importDefaults.repoProvider\",\n \"is not a supported provider\",\n );\n }\n if (\n typeof importDefaults.placeholderEventSource !== \"string\" ||\n !EVENT_SOURCE_PATTERN.test(importDefaults.placeholderEventSource)\n ) {\n check.fail(\n \"importExport.importDefaults.placeholderEventSource\",\n \"must be a lowercase event source\",\n );\n }\n }\n}\n\nfunction checkEndpoints(check: InterfaceChecker, endpoints: unknown): void {\n if (!check.record(endpoints, \"endpoints\")) return;\n const allowed = [...PLAIN_ENDPOINT_NAMES, ...ID_ENDPOINT_NAMES];\n check.closed(endpoints, allowed, \"endpoints\");\n\n const endpointAt = (name: string): string | null => {\n const value = endpoints[name];\n if (\n typeof value !== \"string\" ||\n !ENDPOINT_PATTERN.test(value) ||\n value.includes(\"//\")\n ) {\n check.fail(`endpoints.${name}`, \"must be a rooted service-relative path\");\n return null;\n }\n return value;\n };\n\n PLAIN_ENDPOINT_NAMES.forEach((name) => {\n const value = endpointAt(name);\n if (value !== null && /[{}]/.test(value)) {\n check.fail(`endpoints.${name}`, \"must not carry a substitution\");\n }\n });\n ID_ENDPOINT_NAMES.forEach((name) => {\n const value = endpointAt(name);\n if (value !== null && !ID_ENDPOINT_PATTERN.test(value)) {\n check.fail(`endpoints.${name}`, \"must carry exactly one {id}\");\n }\n });\n}\n\nfunction checkSlugList(\n check: InterfaceChecker,\n value: unknown,\n path: string,\n): string[] {\n if (!Array.isArray(value) || value.length === 0) {\n check.fail(path, \"must be a non-empty array\");\n return [];\n }\n const slugs = value.filter((item): item is string => {\n if (typeof item === \"string\" && SLUG_PATTERN.test(item)) return true;\n check.fail(path, \"must contain only lowercase slugs\");\n return false;\n });\n if (new Set(slugs).size !== slugs.length) {\n check.fail(path, \"must not repeat an id\");\n }\n return slugs;\n}\n\nconst TOP_LEVEL_KEYS = [\n \"version\",\n \"routes\",\n \"navigation\",\n \"pages\",\n \"docsUrl\",\n \"attributes\",\n \"importExport\",\n \"endpoints\",\n \"featuredAutomationIds\",\n \"responderIntegrationIds\",\n];\n\n/**\n * Decide whether this host will act on a published interface manifest.\n *\n * The version is checked first and fails closed: a format this host does not\n * recognise is refused rather than interpreted with today's rules.\n */\nexport function validateInterfaceManifest(\n candidate: unknown,\n context: InterfaceValidationContext,\n): InterfaceValidationResult {\n if (!isRecord(candidate)) {\n return { valid: false, errors: [\"interface: must be an object\"] };\n }\n if (candidate.version !== INTERFACE_VERSION) {\n return {\n valid: false,\n errors: [`interface.version: must be \"${INTERFACE_VERSION}\"`],\n };\n }\n\n const check = new InterfaceChecker();\n check.closed(candidate, TOP_LEVEL_KEYS, \"interface\");\n\n checkRoutes(check, candidate.routes, context.mountedRoutes);\n checkNavigation(check, candidate.navigation);\n checkPages(check, candidate.pages);\n if (\n typeof candidate.docsUrl !== \"string\" ||\n !candidate.docsUrl.startsWith(DOCS_URL_PREFIX)\n ) {\n check.fail(\"docsUrl\", `must start with ${DOCS_URL_PREFIX}`);\n }\n checkAttributes(check, candidate.attributes);\n checkImportExport(check, candidate.importExport);\n checkEndpoints(check, candidate.endpoints);\n\n checkSlugList(\n check,\n candidate.featuredAutomationIds,\n \"featuredAutomationIds\",\n ).forEach((id) => {\n if (!context.catalogIds.has(id)) {\n check.fail(\"featuredAutomationIds\", `${id} is not a catalog entry`);\n }\n });\n checkSlugList(\n check,\n candidate.responderIntegrationIds,\n \"responderIntegrationIds\",\n );\n\n return { valid: check.errors.length === 0, errors: check.errors };\n}\n"],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Admission policy for the extension-published Automation interface manifest.
3
+ *
4
+ * Like a setup entry, the interface manifest is data authored in a different
5
+ * repository that instructs this host to render copy, build links, and address
6
+ * requests, so the host decides what it is *permitted* to state. Admission is
7
+ * all-or-nothing: one bad field rejects the whole manifest, and the host falls
8
+ * back to its own defaults rather than rendering a partially-trusted mix.
9
+ */
10
+ import type { InterfaceRoutes } from "./types";
11
+ export interface InterfaceValidationContext {
12
+ /** Ids of the published automation catalog, for the featured-list check. */
13
+ catalogIds: ReadonlySet<string>;
14
+ /** The routes this host has registrations for. A manifest must match them. */
15
+ mountedRoutes: InterfaceRoutes;
16
+ }
17
+ export interface InterfaceValidationResult {
18
+ valid: boolean;
19
+ errors: string[];
20
+ }
21
+ /**
22
+ * Decide whether this host will act on a published interface manifest.
23
+ *
24
+ * The version is checked first and fails closed: a format this host does not
25
+ * recognise is refused rather than interpreted with today's rules.
26
+ */
27
+ export declare function validateInterfaceManifest(candidate: unknown, context: InterfaceValidationContext): InterfaceValidationResult;
@@ -0,0 +1,182 @@
1
+ //#region src/manifests/interface-validation.ts
2
+ var e = /<[A-Za-z/!]/, t = /^[a-z0-9]+(-[a-z0-9]+)*$/, n = "https://docs.openhands.dev/", r = /^[a-z][a-z-]*$/, i = /^\.[a-z][a-z.]*json$/, a = /^[a-z0-9][a-z0-9.-]*$/, o = /^\/[A-Za-z0-9/{}_-]*$/, s = /^[^{}]*\{id\}[^{}]*$/, c = [
3
+ "github",
4
+ "gitlab",
5
+ "bitbucket"
6
+ ], l = {
7
+ name: {
8
+ type: "text",
9
+ required: !0
10
+ },
11
+ prompt: {
12
+ type: "textarea",
13
+ required: !1
14
+ },
15
+ model: {
16
+ type: "llm-profile",
17
+ required: !1
18
+ },
19
+ timeout: {
20
+ type: "number",
21
+ required: !1
22
+ },
23
+ schedule: {
24
+ type: "schedule",
25
+ required: !1
26
+ }
27
+ }, u = Object.keys(l), d = [
28
+ "list",
29
+ "health",
30
+ "capabilities",
31
+ "validate",
32
+ "createPrompt",
33
+ "createPlugin"
34
+ ], f = [
35
+ "detail",
36
+ "dispatch",
37
+ "runs",
38
+ "tarball"
39
+ ];
40
+ function p(e) {
41
+ return typeof e == "object" && !!e && !Array.isArray(e);
42
+ }
43
+ var m = class {
44
+ errors = [];
45
+ fail(e, t) {
46
+ return this.errors.push(`${e}: ${t}`), !1;
47
+ }
48
+ copy(t, n) {
49
+ return typeof t != "string" || t.length === 0 ? this.fail(n, "must be a non-empty string") : e.test(t) ? this.fail(n, "must not contain markup") : !0;
50
+ }
51
+ closed(e, t, n) {
52
+ Object.keys(e).filter((e) => !t.includes(e)).forEach((e) => this.fail(`${n}.${e}`, "is not allowed"));
53
+ }
54
+ record(e, t) {
55
+ return p(e) ? !0 : (this.fail(t, "must be an object"), !1);
56
+ }
57
+ };
58
+ function h(e, t, n) {
59
+ e.record(t, "routes") && (e.closed(t, [
60
+ "list",
61
+ "setup",
62
+ "detail"
63
+ ], "routes"), [
64
+ "list",
65
+ "setup",
66
+ "detail"
67
+ ].forEach((r) => {
68
+ t[r] !== n[r] && e.fail(`routes.${r}`, `must be "${n[r]}"`);
69
+ }));
70
+ }
71
+ function g(e, t) {
72
+ if (e.record(t, "navigation") && (e.closed(t, ["sidebar", "commandMenu"], "navigation"), e.record(t.sidebar, "navigation.sidebar") && (e.closed(t.sidebar, ["label"], "navigation.sidebar"), e.copy(t.sidebar.label, "navigation.sidebar.label")), e.record(t.commandMenu, "navigation.commandMenu"))) {
73
+ let n = t.commandMenu;
74
+ e.closed(n, [
75
+ "title",
76
+ "description",
77
+ "keywords"
78
+ ], "navigation.commandMenu"), e.copy(n.title, "navigation.commandMenu.title"), e.copy(n.description, "navigation.commandMenu.description"), e.copy(n.keywords, "navigation.commandMenu.keywords");
79
+ }
80
+ }
81
+ function _(e, t) {
82
+ e.record(t, "pages") && (e.closed(t, [
83
+ "list",
84
+ "detail",
85
+ "edit"
86
+ ], "pages"), e.record(t.list, "pages.list") && (e.closed(t.list, ["title", "subtitle"], "pages.list"), e.copy(t.list.title, "pages.list.title"), e.copy(t.list.subtitle, "pages.list.subtitle")), e.record(t.detail, "pages.detail") && (e.closed(t.detail, ["backLabel"], "pages.detail"), e.copy(t.detail.backLabel, "pages.detail.backLabel")), e.record(t.edit, "pages.edit") && (e.closed(t.edit, ["title"], "pages.edit"), e.copy(t.edit.title, "pages.edit.title")));
87
+ }
88
+ function v(e, t, n, r) {
89
+ if (!e.record(n, r)) return;
90
+ e.closed(n, [
91
+ "type",
92
+ "label",
93
+ "help",
94
+ "required",
95
+ "constraints"
96
+ ], r);
97
+ let { type: i, label: a, help: o, required: s, constraints: c } = n;
98
+ if (i !== t.type && e.fail(`${r}.type`, `must be "${t.type}", the control this host renders`), e.copy(a, `${r}.label`), o !== void 0 && e.copy(o, `${r}.help`), s !== t.required && e.fail(`${r}.required`, `must be ${t.required}, what this host enforces`), c !== void 0) {
99
+ if (t.type !== "number") e.fail(`${r}.constraints`, "is only allowed on a number attribute");
100
+ else if (e.record(c, `${r}.constraints`)) {
101
+ e.closed(c, ["min", "max"], `${r}.constraints`);
102
+ let { min: t, max: n } = c;
103
+ t !== void 0 && t !== 1 && e.fail(`${r}.constraints.min`, "must be the 1 this host enforces"), n !== void 0 && (!Number.isInteger(n) || n < 1) && e.fail(`${r}.constraints.max`, "must be a positive integer");
104
+ }
105
+ }
106
+ }
107
+ function y(e, t) {
108
+ if (!e.record(t, "attributes")) return;
109
+ let n = Object.keys(t);
110
+ n.length === 0 && e.fail("attributes", "must declare at least one attribute"), n.forEach((n) => {
111
+ if (!u.includes(n)) {
112
+ e.fail(`attributes.${n}`, "is not a settable attribute");
113
+ return;
114
+ }
115
+ v(e, l[n], t[n], `attributes.${n}`);
116
+ });
117
+ }
118
+ function b(e, t) {
119
+ if (!e.record(t, "importExport")) return;
120
+ e.closed(t, [
121
+ "fileKind",
122
+ "fileVersion",
123
+ "filenameSuffix",
124
+ "importDefaults"
125
+ ], "importExport");
126
+ let { fileKind: n, fileVersion: o, filenameSuffix: s, importDefaults: l } = t;
127
+ (typeof n != "string" || !r.test(n)) && e.fail("importExport.fileKind", "must be a lowercase slug"), o !== 1 && e.fail("importExport.fileVersion", "must be 1"), (typeof s != "string" || !i.test(s)) && e.fail("importExport.filenameSuffix", "must be a .json suffix"), e.record(l, "importExport.importDefaults") && (e.closed(l, ["repoProvider", "placeholderEventSource"], "importExport.importDefaults"), (typeof l.repoProvider != "string" || !c.includes(l.repoProvider)) && e.fail("importExport.importDefaults.repoProvider", "is not a supported provider"), (typeof l.placeholderEventSource != "string" || !a.test(l.placeholderEventSource)) && e.fail("importExport.importDefaults.placeholderEventSource", "must be a lowercase event source"));
128
+ }
129
+ function x(e, t) {
130
+ if (!e.record(t, "endpoints")) return;
131
+ let n = [...d, ...f];
132
+ e.closed(t, n, "endpoints");
133
+ let r = (n) => {
134
+ let r = t[n];
135
+ return typeof r != "string" || !o.test(r) || r.includes("//") ? (e.fail(`endpoints.${n}`, "must be a rooted service-relative path"), null) : r;
136
+ };
137
+ d.forEach((t) => {
138
+ let n = r(t);
139
+ n !== null && /[{}]/.test(n) && e.fail(`endpoints.${t}`, "must not carry a substitution");
140
+ }), f.forEach((t) => {
141
+ let n = r(t);
142
+ n !== null && !s.test(n) && e.fail(`endpoints.${t}`, "must carry exactly one {id}");
143
+ });
144
+ }
145
+ function S(e, n, r) {
146
+ if (!Array.isArray(n) || n.length === 0) return e.fail(r, "must be a non-empty array"), [];
147
+ let i = n.filter((n) => typeof n == "string" && t.test(n) ? !0 : (e.fail(r, "must contain only lowercase slugs"), !1));
148
+ return new Set(i).size !== i.length && e.fail(r, "must not repeat an id"), i;
149
+ }
150
+ var C = [
151
+ "version",
152
+ "routes",
153
+ "navigation",
154
+ "pages",
155
+ "docsUrl",
156
+ "attributes",
157
+ "importExport",
158
+ "endpoints",
159
+ "featuredAutomationIds",
160
+ "responderIntegrationIds"
161
+ ];
162
+ function w(e, t) {
163
+ if (!p(e)) return {
164
+ valid: !1,
165
+ errors: ["interface: must be an object"]
166
+ };
167
+ if (e.version !== "1.0") return {
168
+ valid: !1,
169
+ errors: ["interface.version: must be \"1.0\""]
170
+ };
171
+ let r = new m();
172
+ return r.closed(e, C, "interface"), h(r, e.routes, t.mountedRoutes), g(r, e.navigation), _(r, e.pages), (typeof e.docsUrl != "string" || !e.docsUrl.startsWith(n)) && r.fail("docsUrl", `must start with ${n}`), y(r, e.attributes), b(r, e.importExport), x(r, e.endpoints), S(r, e.featuredAutomationIds, "featuredAutomationIds").forEach((e) => {
173
+ t.catalogIds.has(e) || r.fail("featuredAutomationIds", `${e} is not a catalog entry`);
174
+ }), S(r, e.responderIntegrationIds, "responderIntegrationIds"), {
175
+ valid: r.errors.length === 0,
176
+ errors: r.errors
177
+ };
178
+ }
179
+ //#endregion
180
+ export { w as validateInterfaceManifest };
181
+
182
+ //# sourceMappingURL=interface-validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interface-validation.js","names":[],"sources":["../../src/manifests/interface-validation.ts"],"sourcesContent":["/**\n * Admission policy for the extension-published Automation interface manifest.\n *\n * Like a setup entry, the interface manifest is data authored in a different\n * repository that instructs this host to render copy, build links, and address\n * requests, so the host decides what it is *permitted* to state. Admission is\n * all-or-nothing: one bad field rejects the whole manifest, and the host falls\n * back to its own defaults rather than rendering a partially-trusted mix.\n */\n\nimport type {\n AutomationAttributeName,\n InterfaceAttributeType,\n InterfaceRoutes,\n} from \"./types\";\nimport { INTERFACE_VERSION } from \"./types\";\n\n/** Copy must never be able to inject markup into the host. */\nconst MARKUP_PATTERN = /<[A-Za-z/!]/;\nconst SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;\n/** The one URL the manifest may state, pinned to the product documentation. */\nconst DOCS_URL_PREFIX = \"https://docs.openhands.dev/\";\nconst FILE_KIND_PATTERN = /^[a-z][a-z-]*$/;\nconst FILENAME_SUFFIX_PATTERN = /^\\.[a-z][a-z.]*json$/;\nconst EVENT_SOURCE_PATTERN = /^[a-z0-9][a-z0-9.-]*$/;\n/** Service-relative only: rooted, no traversal, no query, no fragment. */\nconst ENDPOINT_PATTERN = /^\\/[A-Za-z0-9/{}_-]*$/;\n/** Exactly one `{id}` substitution and no other braces. */\nconst ID_ENDPOINT_PATTERN = /^[^{}]*\\{id\\}[^{}]*$/;\n\nconst GIT_PROVIDERS = [\"github\", \"gitlab\", \"bitbucket\"] as const;\n/**\n * The attribute semantics this host's edit dialog implements: the control it\n * renders per property and the requiredness it enforces. Admission pins a\n * declared attribute to exactly these, so an admitted manifest can never\n * promise a control, a requiredness, or (below) a minimum the form would\n * silently ignore.\n */\nconst HOST_ATTRIBUTES: Record<\n AutomationAttributeName,\n { type: InterfaceAttributeType; required: boolean }\n> = {\n name: { type: \"text\", required: true },\n prompt: { type: \"textarea\", required: false },\n model: { type: \"llm-profile\", required: false },\n timeout: { type: \"number\", required: false },\n schedule: { type: \"schedule\", required: false },\n};\nconst ATTRIBUTE_NAMES = Object.keys(HOST_ATTRIBUTES);\nconst PLAIN_ENDPOINT_NAMES = [\n \"list\",\n \"health\",\n \"capabilities\",\n \"validate\",\n \"createPrompt\",\n \"createPlugin\",\n] as const;\nconst ID_ENDPOINT_NAMES = [\"detail\", \"dispatch\", \"runs\", \"tarball\"] as const;\n\nexport interface InterfaceValidationContext {\n /** Ids of the published automation catalog, for the featured-list check. */\n catalogIds: ReadonlySet<string>;\n /** The routes this host has registrations for. A manifest must match them. */\n mountedRoutes: InterfaceRoutes;\n}\n\nexport interface InterfaceValidationResult {\n valid: boolean;\n errors: string[];\n}\n\ntype Rec = Record<string, unknown>;\n\nfunction isRecord(value: unknown): value is Rec {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nclass InterfaceChecker {\n readonly errors: string[] = [];\n\n fail(path: string, reason: string): false {\n this.errors.push(`${path}: ${reason}`);\n return false;\n }\n\n /** Literal user-visible copy. Carries no markup. */\n copy(value: unknown, path: string): boolean {\n if (typeof value !== \"string\" || value.length === 0) {\n return this.fail(path, \"must be a non-empty string\");\n }\n if (MARKUP_PATTERN.test(value)) {\n return this.fail(path, \"must not contain markup\");\n }\n return true;\n }\n\n /** A closed object: every present key must be an expected one. */\n closed(container: Rec, allowed: readonly string[], path: string): void {\n Object.keys(container)\n .filter((key) => !allowed.includes(key))\n .forEach((key) => this.fail(`${path}.${key}`, \"is not allowed\"));\n }\n\n record(value: unknown, path: string): value is Rec {\n if (isRecord(value)) return true;\n this.fail(path, \"must be an object\");\n return false;\n }\n}\n\nfunction checkRoutes(\n check: InterfaceChecker,\n routes: unknown,\n mounted: InterfaceRoutes,\n): void {\n if (!check.record(routes, \"routes\")) return;\n check.closed(routes, [\"list\", \"setup\", \"detail\"], \"routes\");\n\n // The host serves what it has registrations for, so a declared route must be\n // exactly the mounted shape — the manifest owns link construction, not the\n // router table.\n ([\"list\", \"setup\", \"detail\"] as const).forEach((name) => {\n if (routes[name] !== mounted[name]) {\n check.fail(`routes.${name}`, `must be \"${mounted[name]}\"`);\n }\n });\n}\n\nfunction checkNavigation(check: InterfaceChecker, navigation: unknown): void {\n if (!check.record(navigation, \"navigation\")) return;\n check.closed(navigation, [\"sidebar\", \"commandMenu\"], \"navigation\");\n\n if (check.record(navigation.sidebar, \"navigation.sidebar\")) {\n check.closed(navigation.sidebar, [\"label\"], \"navigation.sidebar\");\n check.copy(navigation.sidebar.label, \"navigation.sidebar.label\");\n }\n if (check.record(navigation.commandMenu, \"navigation.commandMenu\")) {\n const menu = navigation.commandMenu;\n check.closed(\n menu,\n [\"title\", \"description\", \"keywords\"],\n \"navigation.commandMenu\",\n );\n check.copy(menu.title, \"navigation.commandMenu.title\");\n check.copy(menu.description, \"navigation.commandMenu.description\");\n check.copy(menu.keywords, \"navigation.commandMenu.keywords\");\n }\n}\n\nfunction checkPages(check: InterfaceChecker, pages: unknown): void {\n if (!check.record(pages, \"pages\")) return;\n check.closed(pages, [\"list\", \"detail\", \"edit\"], \"pages\");\n\n if (check.record(pages.list, \"pages.list\")) {\n check.closed(pages.list, [\"title\", \"subtitle\"], \"pages.list\");\n check.copy(pages.list.title, \"pages.list.title\");\n check.copy(pages.list.subtitle, \"pages.list.subtitle\");\n }\n if (check.record(pages.detail, \"pages.detail\")) {\n check.closed(pages.detail, [\"backLabel\"], \"pages.detail\");\n check.copy(pages.detail.backLabel, \"pages.detail.backLabel\");\n }\n if (check.record(pages.edit, \"pages.edit\")) {\n check.closed(pages.edit, [\"title\"], \"pages.edit\");\n check.copy(pages.edit.title, \"pages.edit.title\");\n }\n}\n\nfunction checkAttribute(\n check: InterfaceChecker,\n host: { type: InterfaceAttributeType; required: boolean },\n attribute: unknown,\n path: string,\n): void {\n if (!check.record(attribute, path)) return;\n check.closed(\n attribute,\n [\"type\", \"label\", \"help\", \"required\", \"constraints\"],\n path,\n );\n\n const { type, label, help, required, constraints } = attribute;\n if (type !== host.type) {\n check.fail(\n `${path}.type`,\n `must be \"${host.type}\", the control this host renders`,\n );\n }\n check.copy(label, `${path}.label`);\n if (help !== undefined) check.copy(help, `${path}.help`);\n if (required !== host.required) {\n check.fail(\n `${path}.required`,\n `must be ${host.required}, what this host enforces`,\n );\n }\n\n if (constraints !== undefined) {\n if (host.type !== \"number\") {\n check.fail(\n `${path}.constraints`,\n \"is only allowed on a number attribute\",\n );\n } else if (check.record(constraints, `${path}.constraints`)) {\n check.closed(constraints, [\"min\", \"max\"], `${path}.constraints`);\n const { min, max } = constraints;\n // The form validates a positive integer, i.e. an effective minimum of 1;\n // any other declared minimum would be a promise it does not keep.\n if (min !== undefined && min !== 1) {\n check.fail(\n `${path}.constraints.min`,\n \"must be the 1 this host enforces\",\n );\n }\n if (\n max !== undefined &&\n (!Number.isInteger(max) || (max as number) < 1)\n ) {\n check.fail(`${path}.constraints.max`, \"must be a positive integer\");\n }\n }\n }\n}\n\nfunction checkAttributes(check: InterfaceChecker, attributes: unknown): void {\n if (!check.record(attributes, \"attributes\")) return;\n const names = Object.keys(attributes);\n if (names.length === 0) {\n check.fail(\"attributes\", \"must declare at least one attribute\");\n }\n names.forEach((name) => {\n if (!ATTRIBUTE_NAMES.includes(name)) {\n check.fail(`attributes.${name}`, \"is not a settable attribute\");\n return;\n }\n checkAttribute(\n check,\n HOST_ATTRIBUTES[name as AutomationAttributeName],\n attributes[name],\n `attributes.${name}`,\n );\n });\n}\n\nfunction checkImportExport(\n check: InterfaceChecker,\n importExport: unknown,\n): void {\n if (!check.record(importExport, \"importExport\")) return;\n check.closed(\n importExport,\n [\"fileKind\", \"fileVersion\", \"filenameSuffix\", \"importDefaults\"],\n \"importExport\",\n );\n\n const { fileKind, fileVersion, filenameSuffix, importDefaults } =\n importExport;\n if (typeof fileKind !== \"string\" || !FILE_KIND_PATTERN.test(fileKind)) {\n check.fail(\"importExport.fileKind\", \"must be a lowercase slug\");\n }\n if (fileVersion !== 1) {\n check.fail(\"importExport.fileVersion\", \"must be 1\");\n }\n if (\n typeof filenameSuffix !== \"string\" ||\n !FILENAME_SUFFIX_PATTERN.test(filenameSuffix)\n ) {\n check.fail(\"importExport.filenameSuffix\", \"must be a .json suffix\");\n }\n if (check.record(importDefaults, \"importExport.importDefaults\")) {\n check.closed(\n importDefaults,\n [\"repoProvider\", \"placeholderEventSource\"],\n \"importExport.importDefaults\",\n );\n if (\n typeof importDefaults.repoProvider !== \"string\" ||\n !(GIT_PROVIDERS as readonly string[]).includes(\n importDefaults.repoProvider,\n )\n ) {\n check.fail(\n \"importExport.importDefaults.repoProvider\",\n \"is not a supported provider\",\n );\n }\n if (\n typeof importDefaults.placeholderEventSource !== \"string\" ||\n !EVENT_SOURCE_PATTERN.test(importDefaults.placeholderEventSource)\n ) {\n check.fail(\n \"importExport.importDefaults.placeholderEventSource\",\n \"must be a lowercase event source\",\n );\n }\n }\n}\n\nfunction checkEndpoints(check: InterfaceChecker, endpoints: unknown): void {\n if (!check.record(endpoints, \"endpoints\")) return;\n const allowed = [...PLAIN_ENDPOINT_NAMES, ...ID_ENDPOINT_NAMES];\n check.closed(endpoints, allowed, \"endpoints\");\n\n const endpointAt = (name: string): string | null => {\n const value = endpoints[name];\n if (\n typeof value !== \"string\" ||\n !ENDPOINT_PATTERN.test(value) ||\n value.includes(\"//\")\n ) {\n check.fail(`endpoints.${name}`, \"must be a rooted service-relative path\");\n return null;\n }\n return value;\n };\n\n PLAIN_ENDPOINT_NAMES.forEach((name) => {\n const value = endpointAt(name);\n if (value !== null && /[{}]/.test(value)) {\n check.fail(`endpoints.${name}`, \"must not carry a substitution\");\n }\n });\n ID_ENDPOINT_NAMES.forEach((name) => {\n const value = endpointAt(name);\n if (value !== null && !ID_ENDPOINT_PATTERN.test(value)) {\n check.fail(`endpoints.${name}`, \"must carry exactly one {id}\");\n }\n });\n}\n\nfunction checkSlugList(\n check: InterfaceChecker,\n value: unknown,\n path: string,\n): string[] {\n if (!Array.isArray(value) || value.length === 0) {\n check.fail(path, \"must be a non-empty array\");\n return [];\n }\n const slugs = value.filter((item): item is string => {\n if (typeof item === \"string\" && SLUG_PATTERN.test(item)) return true;\n check.fail(path, \"must contain only lowercase slugs\");\n return false;\n });\n if (new Set(slugs).size !== slugs.length) {\n check.fail(path, \"must not repeat an id\");\n }\n return slugs;\n}\n\nconst TOP_LEVEL_KEYS = [\n \"version\",\n \"routes\",\n \"navigation\",\n \"pages\",\n \"docsUrl\",\n \"attributes\",\n \"importExport\",\n \"endpoints\",\n \"featuredAutomationIds\",\n \"responderIntegrationIds\",\n];\n\n/**\n * Decide whether this host will act on a published interface manifest.\n *\n * The version is checked first and fails closed: a format this host does not\n * recognise is refused rather than interpreted with today's rules.\n */\nexport function validateInterfaceManifest(\n candidate: unknown,\n context: InterfaceValidationContext,\n): InterfaceValidationResult {\n if (!isRecord(candidate)) {\n return { valid: false, errors: [\"interface: must be an object\"] };\n }\n if (candidate.version !== INTERFACE_VERSION) {\n return {\n valid: false,\n errors: [`interface.version: must be \"${INTERFACE_VERSION}\"`],\n };\n }\n\n const check = new InterfaceChecker();\n check.closed(candidate, TOP_LEVEL_KEYS, \"interface\");\n\n checkRoutes(check, candidate.routes, context.mountedRoutes);\n checkNavigation(check, candidate.navigation);\n checkPages(check, candidate.pages);\n if (\n typeof candidate.docsUrl !== \"string\" ||\n !candidate.docsUrl.startsWith(DOCS_URL_PREFIX)\n ) {\n check.fail(\"docsUrl\", `must start with ${DOCS_URL_PREFIX}`);\n }\n checkAttributes(check, candidate.attributes);\n checkImportExport(check, candidate.importExport);\n checkEndpoints(check, candidate.endpoints);\n\n checkSlugList(\n check,\n candidate.featuredAutomationIds,\n \"featuredAutomationIds\",\n ).forEach((id) => {\n if (!context.catalogIds.has(id)) {\n check.fail(\"featuredAutomationIds\", `${id} is not a catalog entry`);\n }\n });\n checkSlugList(\n check,\n candidate.responderIntegrationIds,\n \"responderIntegrationIds\",\n );\n\n return { valid: check.errors.length === 0, errors: check.errors };\n}\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The action bridge — the only channel through which a manifest can make this
3
+ * host *do* something.
4
+ *
5
+ * A manifest is data from another repository, so it never describes a request.
6
+ * It chooses between the two outcomes this host offers, and it chooses by
7
+ * declaring a `mode`: a direct entry produces a create request the host derives,
8
+ * an assisted entry hands setup to a conversation.
9
+ */
10
+ import type { SetupEntry, SetupFormValues, SetupRequestBody } from "./types";
11
+ export interface SetupActionResult {
12
+ /** The created resource, or the conversation that will finish setup. */
13
+ response: Record<string, unknown>;
14
+ }
15
+ export declare function useSetupAction(): (entry: SetupEntry, values: SetupFormValues, payload: SetupRequestBody | null) => Promise<SetupActionResult>;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Stage 2 — decide whether a deployment can run what a manifest requires.
3
+ *
4
+ * The manifest states what it needs — the features under `requires.features`,
5
+ * and, implicitly, the trigger kinds its form declares. The deployment states
6
+ * what it supports. Neither side is interpreted here beyond set membership, so
7
+ * this stays neutral about what any particular capability means.
8
+ */
9
+ import type { DeploymentCapabilities, SetupEntry } from "./types";
10
+ /**
11
+ * "unknown" is a real outcome, not an error: a deployment that cannot be asked
12
+ * must not be treated as one that answered no.
13
+ */
14
+ export type SetupCapabilitySupport = boolean | "unknown";
15
+ export interface SetupCapabilityAssessment {
16
+ supported: boolean;
17
+ /**
18
+ * Requirement names the deployment did not report, so a block can say which
19
+ * ones rather than only that there were some. Empty when a deployment that
20
+ * reports it is not accepting work blocks the entry, because then no single
21
+ * requirement is the reason.
22
+ */
23
+ unmet: string[];
24
+ }
25
+ export declare function assessCapabilityRequirements(entry: SetupEntry, reported: DeploymentCapabilities): SetupCapabilityAssessment;