@openhands/agent-canvas 1.16.0 → 1.17.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.
- package/README.md +1 -1
- package/README.windows.md +2 -2
- package/build/assets/{acp-providers-CX3Lh9gj.js → acp-providers-DrjNsPOH.js} +1 -1
- package/build/assets/active-backend-context-rZiLJRUc.js +2 -0
- package/build/assets/{agent-context-settings-DSZlXrLj.js → agent-context-settings-CJUrcBut.js} +1 -1
- package/build/assets/agent-profiles-service.api-Biieml2L.js +1 -0
- package/build/assets/agent-profiles-settings-x6RvPsq7.js +1 -0
- package/build/assets/agent-server-client-options-BikiNwTa.js +1 -0
- package/build/assets/agent-server-conversation-service.api-DYFsNHKf.js +88 -0
- package/build/assets/agent-settings-BJyovpJ_.js +1 -0
- package/build/assets/agent-settings-CpY3zObk.js +2 -0
- package/build/assets/{alert-banner-HP6mX-Vr.js → alert-banner-vARz1idS.js} +1 -1
- package/build/assets/{api-error-message-B-XdSk3b.js → api-error-message-Ve79qDcN.js} +1 -1
- package/build/assets/api-key-entry-screen-CsMuBhoP.js +1 -0
- package/build/assets/{app-settings-dbnneVaV.js → app-settings-BaJ7nfHt.js} +1 -1
- package/build/assets/automation-detail-CVoGDrKq.js +3 -0
- package/build/assets/{automation-export-D3a3of4G.js → automation-export-DxY47m3T.js} +1 -1
- package/build/assets/automation-git-sync-CRMP9Bbm.js +1 -0
- package/build/assets/automation-setup-route-072beLz1.js +3 -0
- package/build/assets/{automation-templates-BRppNh99.js → automation-templates-4C4PM8K7.js} +1 -1
- package/build/assets/automations-list-xuqTSQL4.js +1 -0
- package/build/assets/{back-nav-button-Cjl_hoxq.js → back-nav-button-B8tH0BEp.js} +1 -1
- package/build/assets/backend-form-modal-0aJmWdsa.js +1 -0
- package/build/assets/{backend-synced-settings-badge-DPkz30BE.js → backend-synced-settings-badge-B7rKWBir.js} +1 -1
- package/build/assets/{badge-BU3S0VtE.js → badge-CcGx2TaV.js} +1 -1
- package/build/assets/{base-modal-D_L0oEbG.js → base-modal-DTULovNc.js} +1 -1
- package/build/assets/bot-CLMxGFaq.js +1 -0
- package/build/assets/{brand-button-BAfk4wGP.js → brand-button-DxPO4Lf8.js} +1 -1
- package/build/assets/{browser-DSAo0tz1.js → browser-C-_qBasq.js} +2 -2
- package/build/assets/{browser-tab-C9vy56R5.js → browser-tab-DZanIw5m.js} +1 -1
- package/build/assets/{canvas-extension-page-dx1gFrfW.js → canvas-extension-page-o_cWgE9B.js} +1 -1
- package/build/assets/canvas-extensions-DSgNlCha.js +1 -0
- package/build/assets/{canvas-extensions-runtime-2_x5Ezmi.js → canvas-extensions-runtime-DdnkgJXV.js} +1 -1
- package/build/assets/{chat-send-button-MUQqCZCS.js → chat-send-button-B3SFSIg5.js} +1 -1
- package/build/assets/check-fDEfgSzj.js +1 -0
- package/build/assets/chevron-down-CFrWHqu8.js +1 -0
- package/build/assets/chevron-right-C5roOrJu.js +1 -0
- package/build/assets/circle-check-ClTzJTdb.js +1 -0
- package/build/assets/{circle-plus-check-toggle-BO_kFZt3.js → circle-plus-check-toggle-Fr-h-mbL.js} +1 -1
- package/build/assets/{cloud-funnel-analytics-CztBCCPp.js → cloud-funnel-analytics-BQanUEEC.js} +1 -1
- package/build/assets/{cog-D-MPQW6G.js → cog-VBlRo6iw.js} +1 -1
- package/build/assets/command-menu-BT3Ia5Ru.js +1 -0
- package/build/assets/commits-tab-2jhtaohl.js +1 -0
- package/build/assets/{condenser-settings-DmEOje-7.js → condenser-settings-eVKFMvap.js} +1 -1
- package/build/assets/confirmation-modal-gIFHeZt6.js +1 -0
- package/build/assets/{context-menu-list-item-I86vKcmM.js → context-menu-list-item-DDROe12Z.js} +1 -1
- package/build/assets/conversation-BnjJSFf5.js +93 -0
- package/build/assets/conversation-DZ578rOX.js +1 -0
- package/build/assets/conversation-panel-DZ578rOX.js +1 -0
- package/build/assets/{conversation-state-store-B0ImPLHO.js → conversation-state-store-D1_G_9I-.js} +1 -1
- package/build/assets/{conversation-store-Ca3ghXap.js → conversation-store-DVVfkCsk.js} +1 -1
- package/build/assets/{conversation-tab-empty-state-FIbh_lc8.js → conversation-tab-empty-state-DKlzAz5c.js} +1 -1
- package/build/assets/{copy-to-clipboard-button-Ca8iSkkA.js → copy-to-clipboard-button-B3w0X7WH.js} +1 -1
- package/build/assets/{device-verify-Bhn2e_Ye.js → device-verify-BfkCzyFB.js} +1 -1
- package/build/assets/{dropdown-classes-Bc1djN0-.js → dropdown-classes-B9kJVDIk.js} +1 -1
- package/build/assets/edit-automation-modal-BwhJ39JT.js +1 -0
- package/build/assets/empty-state-RZz0OfnY.js +1 -0
- package/build/assets/{entry.client-CqyPPjOq.js → entry.client-Ds8LVp-E.js} +2 -2
- package/build/assets/enum-filter-dropdown-C4pW0ORd.js +1 -0
- package/build/assets/{environment-switch-overlay-BmBRT7gv.js → environment-switch-overlay-P2ZAuzov.js} +1 -1
- package/build/assets/{error-state-Body8Ngd.js → error-state-B_5cgNPJ.js} +1 -1
- package/build/assets/extensions-hub-DsaUr4ca.js +1 -0
- package/build/assets/extensions-navigation-CRMZd-FE.js +1 -0
- package/build/assets/external-link-DQtv7eTh.js +1 -0
- package/build/assets/files-tab-CCgAcVdc.js +1 -0
- package/build/assets/{form-control-classes-Bkh5FGrD.js → form-control-classes-B-z85REi.js} +1 -1
- package/build/assets/get-git-path-EHrSDxTM.js +1 -0
- package/build/assets/git-repo-dropdown-D2WEQ97X.js +1 -0
- package/build/assets/{git-repo-dropdown-4ROKl2cB.js → git-repo-dropdown-DVeCUUXG.js} +1 -1
- package/build/assets/{highlighted-source-view-BFRxGT_Q.js → highlighted-source-view-DjHO3SdW.js} +1 -1
- package/build/assets/home-BorS_vdS.js +1 -0
- package/build/assets/home-aIMbHw5M.js +1 -0
- package/build/assets/home-automation-run-tooltip-CpHNFiFl.js +1 -0
- package/build/assets/home-store-DyXz0w6d.js +1 -0
- package/build/assets/hooks-service-C6dpRtZM.js +1 -0
- package/build/assets/index-home-zKFuKK2B.js +1 -0
- package/build/assets/info-CTIdjttz.js +1 -0
- package/build/assets/install-server-modal-DUiZMc8u.js +1 -0
- package/build/assets/kebab-menu-item-content-mm04SBD3.js +1 -0
- package/build/assets/{key-BmVyxs-2.js → key-CfXbJa8w.js} +1 -1
- package/build/assets/{launch-IrTDEFCs.js → launch-BSIf9KEg.js} +1 -1
- package/build/assets/list-filter-CNpqTWVY.js +1 -0
- package/build/assets/{list-todo-apgLVjMw.js → list-todo-ByXMdY4W.js} +1 -1
- package/build/assets/llm-not-configured-banner-z_QRSeV0.js +27 -0
- package/build/assets/llm-settings-B-5AHMiX.js +1 -0
- package/build/assets/llm-settings-B3SjHDmA.js +1 -0
- package/build/assets/llm-subscription-service-aDNBQ7Gq.js +1 -0
- package/build/assets/loader-circle-Dex47vVW.js +1 -0
- package/build/assets/{loading-spinner-BY9N3pOe.js → loading-spinner-DfHI77BA.js} +1 -1
- package/build/assets/manage-backends-modal-q1NdFisr.js +1 -0
- package/build/assets/manifest-8920b615.js +1 -0
- package/build/assets/{manifest-subpage-layout-CAOYzrYp.js → manifest-subpage-layout-BQQy5ipR.js} +1 -1
- package/build/assets/{map-provider-Cu3Zgumb.js → map-provider-DXvssjw9.js} +1 -1
- package/build/assets/markdown-renderer-rivLvYh-.js +8 -0
- package/build/assets/{mcp-BTs_SXfZ.js → mcp-CEXDhQWn.js} +1 -1
- package/build/assets/{mcp-marketplace-utils-AIBzzHTO.js → mcp-marketplace-utils-Cn_NIMWM.js} +1 -1
- package/build/assets/mcp-page-LGYIICMG.js +9 -0
- package/build/assets/messages-DUSeANAh.js +41 -0
- package/build/assets/{modal-body-BxZjsju9.js → modal-body-DJw4xBki.js} +1 -1
- package/build/assets/{modal-classes-C8eycCwG.js → modal-classes-Dg0ngBPO.js} +1 -1
- package/build/assets/{modal-close-button-DDzdRvCs.js → modal-close-button-B9j4nasm.js} +1 -1
- package/build/assets/{mouse-pointer-click-Co2uyaPA.js → mouse-pointer-click-DEhsSUpu.js} +1 -1
- package/build/assets/onboarding-BjFebfs5.js +1 -0
- package/build/assets/onboarding-modal-DDTjOpyd.js +1 -0
- package/build/assets/organization-service.api-Cyg1Tn03.js +1 -0
- package/build/assets/pencil-D5c47kyN.js +1 -0
- package/build/assets/pin-C7vMagvf.js +1 -0
- package/build/assets/planner-tab-D8f2Y3rn.js +1 -0
- package/build/assets/plug-CeaqeF6u.js +1 -0
- package/build/assets/plugins-management-service-Bh_pBuf-.js +1 -0
- package/build/assets/plus-B3wvU06-.js +1 -0
- package/build/assets/profiles-service.api-BbOK0EkF.js +1 -0
- package/build/assets/{providers-BhMcY5nv.js → providers-2gIIkz2N.js} +1 -1
- package/build/assets/proxy-CBgMd2zf.js +1 -0
- package/build/assets/{puzzle-JM_QaRm0.js → puzzle-DdAqbUrb.js} +1 -1
- package/build/assets/recommended-automations-launcher-YLAQTp4s.js +1 -0
- package/build/assets/refresh-cw-DEQdPCpH.js +1 -0
- package/build/assets/root-Bye_XmEL.js +2 -0
- package/build/assets/root-Cnwwzgo0.css +1 -0
- package/build/assets/root-layout-CSAwxBGM.js +2 -0
- package/build/assets/schema-field-CK0xYNbO.js +1 -0
- package/build/assets/scroll-fade-state-Dt_fx2kf.js +1 -0
- package/build/assets/sdk-section-page-Da73Rs_o.js +1 -0
- package/build/assets/secret-form-CB2VwYYg.js +1 -0
- package/build/assets/secrets-service-Cr8g8xuv.js +1 -0
- package/build/assets/{secrets-settings-B_GpxKW2.js → secrets-settings-Bh0udeKv.js} +1 -1
- package/build/assets/settings-DmyIgRxO.js +1 -0
- package/build/assets/{settings-dropdown-input-BcwylHs2.js → settings-dropdown-input-DVpyD87C.js} +1 -1
- package/build/assets/settings-index-BSJUFmjE.js +1 -0
- package/build/assets/{settings-input-Db-1g8Ql.js → settings-input-C0dd8Utu.js} +1 -1
- package/build/assets/{settings-list-classes-BzeSZbF2.js → settings-list-classes-Ds4jPBHK.js} +1 -1
- package/build/assets/{settings-modal-DfJ3tcJQ.js → settings-modal-yStgEh2o.js} +1 -1
- package/build/assets/settings-service.api-9_xdv_mh.js +1 -0
- package/build/assets/{settings-switch-CS-dJDmA.js → settings-switch-B78Khmyz.js} +1 -1
- package/build/assets/{settings-utils-UYpEdPuD.js → settings-utils-8GIIgOQL.js} +1 -1
- package/build/assets/shared-conversation-DM96X4KV.js +1 -0
- package/build/assets/shield-check-DO7wa_eu.js +1 -0
- package/build/assets/{sidebar-layout-D6fS8-Gl.js → sidebar-layout-EPfWxk11.js} +1 -1
- package/build/assets/sidebar-mobile-menu-toggle-D5GR5xT9.js +1 -0
- package/build/assets/{sidebar-nav-link-DQQcMf1A.js → sidebar-nav-link-_t4_Ef-9.js} +1 -1
- package/build/assets/{sidebar-onboarding-checklist-storage-BBkeI6Bp.js → sidebar-onboarding-checklist-storage-DbDX-Wvj.js} +1 -1
- package/build/assets/{sidebar-store-DGqHkVLw.js → sidebar-store-CSDFK0W5.js} +1 -1
- package/build/assets/{skill-card-pill-row-B27-xyig.js → skill-card-pill-row-989y15jV.js} +1 -1
- package/build/assets/{skill-filter-w__3UCEE.js → skill-filter-BBeZ4JEP.js} +1 -1
- package/build/assets/{skill-icon-badge-RQkq8wvl.js → skill-icon-badge-BDhQ-tQ0.js} +1 -1
- package/build/assets/skill-scope-Dz3kPH3P.js +1 -0
- package/build/assets/skills-plugins-e3CYGdUJ.js +1 -0
- package/build/assets/skills-settings-3e-FuHw_.js +1 -0
- package/build/assets/styled-tooltip-Pd22BIvr.js +1 -0
- package/build/assets/{task-list-tab-C5zcaktH.js → task-list-tab-3Jg8ywch.js} +1 -1
- package/build/assets/{terminal-CNElN42a.js → terminal-onXx0_Qz.js} +2 -2
- package/build/assets/toggle-switch-lwQtxDcm.js +1 -0
- package/build/assets/trash-2-ZhKuTLiz.js +1 -0
- package/build/assets/trash-B6OIe4yX.js +1 -0
- package/build/assets/triangle-alert-BajLPpQg.js +1 -0
- package/build/assets/{typography-v1KN0tzi.js → typography-HLLg_FAn.js} +1 -1
- package/build/assets/usage-tab-Cm-9j3UK.js +1 -0
- package/build/assets/use-acp-credential-form-BARcvjYE.js +1 -0
- package/build/assets/use-activate-agent-profile-BLKML6vQ.js +1 -0
- package/build/assets/use-agent-profiles-1P91KSXz.js +1 -0
- package/build/assets/{use-automation-health-E6NZi9rb.js → use-automation-health-BZkQNEA8.js} +1 -1
- package/build/assets/use-automation-permissions-B_RpvL-s.js +1 -0
- package/build/assets/{use-automations-xijQKIl9.js → use-automations-Cg0PDIZr.js} +1 -1
- package/build/assets/{use-can-manage-org-profiles-CrJP5262.js → use-can-manage-org-profiles-BbN6KfDr.js} +1 -1
- package/build/assets/use-canvas-extensions-CFUpHat6.js +1 -0
- package/build/assets/{use-cloud-current-user-id-CDfAHpPX.js → use-cloud-current-user-id-Dk0rjr63.js} +1 -1
- package/build/assets/use-config-DpafU_QP.js +1 -0
- package/build/assets/use-create-conversation-1YgmAHt-.js +1 -0
- package/build/assets/use-create-secret-D8p9ybQj.js +1 -0
- package/build/assets/{use-get-secrets-K3jmk-LH.js → use-get-secrets-CQ4b0qK8.js} +1 -1
- package/build/assets/use-is-authed-BRPjCRKM.js +1 -0
- package/build/assets/{use-launch-skill-in-chat-BYjYihBP.js → use-launch-skill-in-chat-DeYa1tFM.js} +1 -1
- package/build/assets/use-llm-configured-DGm8FiRR.js +1 -0
- package/build/assets/use-llm-profiles-BTTtpW4z.js +1 -0
- package/build/assets/use-manifest-capabilities-AOWGcu0j.js +1 -0
- package/build/assets/use-onboarding-completion-CoINurkm.js +1 -0
- package/build/assets/use-pinned-home-route-D6krwXjy.js +1 -0
- package/build/assets/use-plugins-marketplace-mLsIDhBv.js +1 -0
- package/build/assets/use-save-agent-profile-CxyO9YG-.js +1 -0
- package/build/assets/{use-save-settings-DYqkKv7T.js → use-save-settings-3U5K9-8x.js} +1 -1
- package/build/assets/use-settings-Dn00UISA.js +1 -0
- package/build/assets/use-settings-nav-items-vrjxTqcu.js +16 -0
- package/build/assets/use-tracking-B9prBsrq.js +3 -0
- package/build/assets/{use-user-conversation-B9Gebw_g.js → use-user-conversation-ZWlXtbZb.js} +1 -1
- package/build/assets/{useInfiniteQuery-C926xzY4.js → useInfiniteQuery-BzrRqjLg.js} +1 -1
- package/build/assets/{useMutation-B-qc391N.js → useMutation-CJuK90oc.js} +1 -1
- package/build/assets/{useQueries-DlbhlH9I.js → useQueries-CykmpT-a.js} +1 -1
- package/build/assets/{useQuery-BSTgq1rl.js → useQuery-S6j94er1.js} +1 -1
- package/build/assets/utils-BOco8mxR.js +1 -0
- package/build/assets/{vendor~conversation-panel~conversation-C8Fn4XOf.js → vendor~conversation-panel~conversation-4eJ0tml3.js} +1 -1
- package/build/assets/vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~b0x1wrp2-Z0lly3FE.js +2 -0
- package/build/assets/vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~czjee3a3-BfkNGovw.js +185 -0
- package/build/assets/{vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~ew9ih1s8-rfV8mQ9m.js → vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~ew9ih1s8-DnDoaYNi.js} +1 -1
- package/build/assets/{vendor~index-home~home~conversation-panel~conversation-hWywRBaR.js → vendor~index-home~home~conversation-panel~conversation-CSFLzmTN.js} +1 -1
- package/build/assets/vendor~root-layout-n8XCEXG8.js +1 -0
- package/build/assets/{vendor~root-layout~conversation-panel~conversation-BMbMgaGu.js → vendor~root-layout~conversation-panel~conversation-3Idg1_U3.js} +1 -1
- package/build/assets/{vendor~root-layout~conversation-panel~conversation~skills-settings~automations-list~shared-~bovqqk8e-BWir3rJs.js → vendor~root-layout~conversation-panel~conversation~skills-settings~automations-list~shared-~bovqqk8e-DkPlXWl9.js} +1 -1
- package/build/assets/{vendor~root-layout~index-home~home~conversation-panel~conversation-j5MFqfSy.js → vendor~root-layout~index-home~home~conversation-panel~conversation-DgQ564nC.js} +1 -1
- package/build/assets/vendor~root-layout~index-home~home~conversation-panel~conversation~mcp~automations-list~aut~ibfbmr9f-CxhwKuAd.js +1 -0
- package/build/assets/{vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~nj1hli3r-DP9wmsjo.js → vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~inibp4qe-CnsDDzeE.js} +1 -1
- package/build/assets/vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~iyia17eu-CV850oio.js +1 -0
- package/build/assets/{vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~skills-setti~eo2nf80n-CUmIwQCN.js → vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~skills-setti~eo2nf80n-BPjI-atc.js} +12 -6
- package/build/assets/{verification-settings-gw_q6vkL.js → verification-settings-u2POabjH.js} +1 -1
- package/build/assets/{wrench-CUEI1krB.js → wrench-CkgI4rxA.js} +1 -1
- package/build/assets/x-By6qCmtY.js +1 -0
- package/build/index.html +4 -4
- package/build/locales/ar/openhands.json +73 -20
- package/build/locales/ca/openhands.json +73 -20
- package/build/locales/de/openhands.json +73 -20
- package/build/locales/en/openhands.json +73 -20
- package/build/locales/es/openhands.json +73 -20
- package/build/locales/fr/openhands.json +73 -20
- package/build/locales/it/openhands.json +73 -20
- package/build/locales/ja/openhands.json +73 -20
- package/build/locales/ko-KR/openhands.json +73 -20
- package/build/locales/no/openhands.json +73 -20
- package/build/locales/pt/openhands.json +73 -20
- package/build/locales/tr/openhands.json +73 -20
- package/build/locales/uk/openhands.json +73 -20
- package/build/locales/zh-CN/openhands.json +73 -20
- package/build/locales/zh-TW/openhands.json +73 -20
- package/config/defaults.json +3 -3
- package/dist/api/agent-server-adapter.cjs +3 -3
- package/dist/api/agent-server-adapter.cjs.map +1 -1
- package/dist/api/agent-server-adapter.d.ts +72 -12
- package/dist/api/agent-server-adapter.js +234 -132
- package/dist/api/agent-server-adapter.js.map +1 -1
- package/dist/api/agent-server-config.cjs +1 -1
- package/dist/api/agent-server-config.cjs.map +1 -1
- package/dist/api/agent-server-config.d.ts +6 -0
- package/dist/api/agent-server-config.js +6 -3
- package/dist/api/agent-server-config.js.map +1 -1
- package/dist/api/automation-service/automation-service.api.cjs +1 -1
- package/dist/api/automation-service/automation-service.api.cjs.map +1 -1
- package/dist/api/automation-service/automation-service.api.d.ts +2 -2
- package/dist/api/automation-service/automation-service.api.js +7 -7
- package/dist/api/automation-service/automation-service.api.js.map +1 -1
- package/dist/api/cloud/provider-connections-service.api.cjs +2 -0
- package/dist/api/cloud/provider-connections-service.api.cjs.map +1 -0
- package/dist/api/cloud/provider-connections-service.api.d.ts +5 -0
- package/dist/api/cloud/provider-connections-service.api.js +50 -0
- package/dist/api/cloud/provider-connections-service.api.js.map +1 -0
- package/dist/api/cloud/skills-service.api.cjs +1 -1
- package/dist/api/cloud/skills-service.api.cjs.map +1 -1
- package/dist/api/cloud/skills-service.api.d.ts +10 -0
- package/dist/api/cloud/skills-service.api.js +11 -1
- package/dist/api/cloud/skills-service.api.js.map +1 -1
- package/dist/api/conversation-metadata-store.cjs +1 -1
- package/dist/api/conversation-metadata-store.cjs.map +1 -1
- package/dist/api/conversation-metadata-store.d.ts +24 -0
- package/dist/api/conversation-metadata-store.js +10 -2
- package/dist/api/conversation-metadata-store.js.map +1 -1
- package/dist/api/conversation-service/agent-server-conversation-service.api.cjs +1 -1
- package/dist/api/conversation-service/agent-server-conversation-service.api.cjs.map +1 -1
- package/dist/api/conversation-service/agent-server-conversation-service.api.d.ts +20 -0
- package/dist/api/conversation-service/agent-server-conversation-service.api.js +126 -77
- package/dist/api/conversation-service/agent-server-conversation-service.api.js.map +1 -1
- package/dist/api/hooks-service.cjs +2 -0
- package/dist/api/hooks-service.cjs.map +1 -0
- package/dist/api/hooks-service.d.ts +13 -0
- package/dist/api/hooks-service.js +20 -0
- package/dist/api/hooks-service.js.map +1 -0
- package/dist/api/provider-connections-service/provider-connections-service.api.cjs +1 -1
- package/dist/api/provider-connections-service/provider-connections-service.api.cjs.map +1 -1
- package/dist/api/provider-connections-service/provider-connections-service.api.js +25 -16
- package/dist/api/provider-connections-service/provider-connections-service.api.js.map +1 -1
- package/dist/api/settings-service/settings-service.api.cjs +1 -1
- package/dist/api/settings-service/settings-service.api.cjs.map +1 -1
- package/dist/api/settings-service/settings-service.api.d.ts +2 -0
- package/dist/api/settings-service/settings-service.api.js +18 -10
- package/dist/api/settings-service/settings-service.api.js.map +1 -1
- package/dist/api/skills-service.cjs +1 -1
- package/dist/api/skills-service.cjs.map +1 -1
- package/dist/api/skills-service.d.ts +7 -0
- package/dist/api/skills-service.js +11 -8
- package/dist/api/skills-service.js.map +1 -1
- package/dist/components/conversation-events/chat/event-message-components/critic-result-display.cjs +1 -1
- package/dist/components/conversation-events/chat/event-message-components/critic-result-display.js +5 -5
- package/dist/components/conversation-events/chat/event-message-components/finish-event-message.cjs +1 -1
- package/dist/components/conversation-events/chat/event-message-components/finish-event-message.cjs.map +1 -1
- package/dist/components/conversation-events/chat/event-message-components/finish-event-message.js +2 -1
- package/dist/components/conversation-events/chat/event-message-components/finish-event-message.js.map +1 -1
- package/dist/components/conversation-events/chat/event-message-components/generic-event-message-wrapper.cjs +1 -1
- package/dist/components/conversation-events/chat/event-message-components/generic-event-message-wrapper.cjs.map +1 -1
- package/dist/components/conversation-events/chat/event-message-components/generic-event-message-wrapper.d.ts +1 -1
- package/dist/components/conversation-events/chat/event-message-components/generic-event-message-wrapper.js +28 -28
- package/dist/components/conversation-events/chat/event-message-components/generic-event-message-wrapper.js.map +1 -1
- package/dist/components/conversation-events/chat/event-message-components/thought-event-message.cjs +1 -1
- package/dist/components/conversation-events/chat/event-message-components/thought-event-message.cjs.map +1 -1
- package/dist/components/conversation-events/chat/event-message-components/thought-event-message.js +2 -1
- package/dist/components/conversation-events/chat/event-message-components/thought-event-message.js.map +1 -1
- package/dist/components/conversation-events/chat/event-message-components/user-assistant-event-message.cjs +1 -1
- package/dist/components/conversation-events/chat/event-message-components/user-assistant-event-message.cjs.map +1 -1
- package/dist/components/conversation-events/chat/event-message-components/user-assistant-event-message.d.ts +1 -1
- package/dist/components/conversation-events/chat/event-message-components/user-assistant-event-message.js +37 -37
- package/dist/components/conversation-events/chat/event-message-components/user-assistant-event-message.js.map +1 -1
- package/dist/components/conversation-events/chat/event-message.cjs +2 -2
- package/dist/components/conversation-events/chat/event-message.cjs.map +1 -1
- package/dist/components/conversation-events/chat/event-message.js +79 -70
- package/dist/components/conversation-events/chat/event-message.js.map +1 -1
- package/dist/components/conversation-events/chat/messages.cjs +1 -1
- package/dist/components/conversation-events/chat/messages.cjs.map +1 -1
- package/dist/components/conversation-events/chat/messages.js +28 -27
- package/dist/components/conversation-events/chat/messages.js.map +1 -1
- package/dist/components/features/automations/automation-card.cjs +1 -1
- package/dist/components/features/automations/automation-card.cjs.map +1 -1
- package/dist/components/features/automations/automation-card.js +100 -94
- package/dist/components/features/automations/automation-card.js.map +1 -1
- package/dist/components/features/automations/automation-list-row.cjs +1 -1
- package/dist/components/features/automations/automation-list-row.cjs.map +1 -1
- package/dist/components/features/automations/automation-list-row.js +88 -77
- package/dist/components/features/automations/automation-list-row.js.map +1 -1
- package/dist/components/features/automations/detail/detail-header.d.ts +3 -1
- package/dist/components/features/automations/detail/run-status-badge.cjs +1 -1
- package/dist/components/features/automations/detail/run-status-badge.cjs.map +1 -1
- package/dist/components/features/automations/detail/run-status-badge.d.ts +2 -2
- package/dist/components/features/automations/detail/run-status-badge.js +82 -53
- package/dist/components/features/automations/detail/run-status-badge.js.map +1 -1
- package/dist/components/features/automations/kebab-menu.cjs +1 -1
- package/dist/components/features/automations/kebab-menu.cjs.map +1 -1
- package/dist/components/features/automations/kebab-menu.js +9 -5
- package/dist/components/features/automations/kebab-menu.js.map +1 -1
- package/dist/components/features/automations/recommended-automations-launcher.cjs +1 -1
- package/dist/components/features/automations/recommended-automations-launcher.cjs.map +1 -1
- package/dist/components/features/automations/recommended-automations-launcher.js +12 -12
- package/dist/components/features/automations/recommended-automations-launcher.js.map +1 -1
- package/dist/components/features/automations/recommended-automations-section.cjs +1 -1
- package/dist/components/features/automations/recommended-automations-section.js +9 -9
- package/dist/components/features/backends/backend-selector.cjs +1 -1
- package/dist/components/features/backends/backend-selector.cjs.map +1 -1
- package/dist/components/features/backends/backend-selector.js +133 -122
- package/dist/components/features/backends/backend-selector.js.map +1 -1
- package/dist/components/features/backends/manage-backends-modal.cjs +1 -1
- package/dist/components/features/backends/manage-backends-modal.cjs.map +1 -1
- package/dist/components/features/backends/manage-backends-modal.js +29 -29
- package/dist/components/features/backends/manage-backends-modal.js.map +1 -1
- package/dist/components/features/chat/change-agent-button.cjs +1 -1
- package/dist/components/features/chat/change-agent-button.js +4 -4
- package/dist/components/features/chat/chat-interface.cjs +2 -2
- package/dist/components/features/chat/chat-interface.cjs.map +1 -1
- package/dist/components/features/chat/chat-interface.js +137 -137
- package/dist/components/features/chat/chat-interface.js.map +1 -1
- package/dist/components/features/chat/chat-message.cjs +1 -1
- package/dist/components/features/chat/chat-message.cjs.map +1 -1
- package/dist/components/features/chat/chat-message.d.ts +2 -1
- package/dist/components/features/chat/chat-message.js +88 -79
- package/dist/components/features/chat/chat-message.js.map +1 -1
- package/dist/components/features/chat/components/context-window-meter.cjs +1 -1
- package/dist/components/features/chat/components/context-window-meter.js +4 -4
- package/dist/components/features/chat/confirmation-mode-enabled.cjs +1 -1
- package/dist/components/features/chat/confirmation-mode-enabled.js +4 -4
- package/dist/components/features/chat/generic-event-message.cjs +1 -1
- package/dist/components/features/chat/generic-event-message.cjs.map +1 -1
- package/dist/components/features/chat/generic-event-message.d.ts +2 -1
- package/dist/components/features/chat/generic-event-message.js +41 -22
- package/dist/components/features/chat/generic-event-message.js.map +1 -1
- package/dist/components/features/chat/interactive-chat-box.cjs +1 -1
- package/dist/components/features/chat/interactive-chat-box.cjs.map +1 -1
- package/dist/components/features/chat/interactive-chat-box.js +20 -19
- package/dist/components/features/chat/interactive-chat-box.js.map +1 -1
- package/dist/components/features/conversation/conversation-overview-git-section.cjs +1 -1
- package/dist/components/features/conversation/conversation-overview-git-section.js +4 -4
- package/dist/components/features/conversation/conversation-overview-skills-panel.cjs +1 -1
- package/dist/components/features/conversation/conversation-overview-skills-panel.js +4 -4
- package/dist/components/features/conversation/conversation-tabs/conversation-tabs-context-menu.cjs +1 -1
- package/dist/components/features/conversation/conversation-tabs/conversation-tabs-context-menu.cjs.map +1 -1
- package/dist/components/features/conversation/conversation-tabs/conversation-tabs-context-menu.js +77 -78
- package/dist/components/features/conversation/conversation-tabs/conversation-tabs-context-menu.js.map +1 -1
- package/dist/components/features/conversation/conversation-tabs/conversation-tabs.cjs +1 -1
- package/dist/components/features/conversation/conversation-tabs/conversation-tabs.cjs.map +1 -1
- package/dist/components/features/conversation/conversation-tabs/conversation-tabs.js +88 -88
- package/dist/components/features/conversation/conversation-tabs/conversation-tabs.js.map +1 -1
- package/dist/components/features/conversation-panel/advanced-conversation-options-modal.cjs +2 -0
- package/dist/components/features/conversation-panel/advanced-conversation-options-modal.cjs.map +1 -0
- package/dist/components/features/conversation-panel/advanced-conversation-options-modal.d.ts +21 -0
- package/dist/components/features/conversation-panel/advanced-conversation-options-modal.js +239 -0
- package/dist/components/features/conversation-panel/advanced-conversation-options-modal.js.map +1 -0
- package/dist/components/features/conversation-panel/conversation-active-tag-filters.cjs +2 -0
- package/dist/components/features/conversation-panel/conversation-active-tag-filters.cjs.map +1 -0
- package/dist/components/features/conversation-panel/conversation-active-tag-filters.d.ts +28 -0
- package/dist/components/features/conversation-panel/conversation-active-tag-filters.js +69 -0
- package/dist/components/features/conversation-panel/conversation-active-tag-filters.js.map +1 -0
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-actions.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-actions.cjs.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-actions.d.ts +2 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-actions.js +19 -18
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-actions.js.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-context-menu.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-context-menu.cjs.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-context-menu.d.ts +2 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-context-menu.js +91 -80
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-context-menu.js.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-preview.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-preview.cjs.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-preview.js +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-preview.js.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card.cjs.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card.d.ts +7 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card.js +87 -81
- package/dist/components/features/conversation-panel/conversation-card/conversation-card.js.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-tag-chips.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-tag-chips.cjs.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-tag-chips.d.ts +2 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-tag-chips.js +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-tag-chips.js.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-tag-display.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-tag-display.cjs.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-tag-display.d.ts +2 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-tag-display.js +2 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-tag-display.js.map +1 -1
- package/dist/components/features/conversation-panel/conversation-layouts-menu.cjs +2 -0
- package/dist/components/features/conversation-panel/conversation-layouts-menu.cjs.map +1 -0
- package/dist/components/features/conversation-panel/conversation-layouts-menu.d.ts +29 -0
- package/dist/components/features/conversation-panel/conversation-layouts-menu.js +177 -0
- package/dist/components/features/conversation-panel/conversation-layouts-menu.js.map +1 -0
- package/dist/components/features/conversation-panel/conversation-panel-list-helpers.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-panel-list-helpers.cjs.map +1 -1
- package/dist/components/features/conversation-panel/conversation-panel-list-helpers.d.ts +37 -0
- package/dist/components/features/conversation-panel/conversation-panel-list-helpers.js +85 -45
- package/dist/components/features/conversation-panel/conversation-panel-list-helpers.js.map +1 -1
- package/dist/components/features/conversation-panel/conversation-panel.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-panel.cjs.map +1 -1
- package/dist/components/features/conversation-panel/conversation-panel.js +296 -290
- package/dist/components/features/conversation-panel/conversation-panel.js.map +1 -1
- package/dist/components/features/conversation-panel/edit-conversation-tags-modal.cjs +2 -0
- package/dist/components/features/conversation-panel/edit-conversation-tags-modal.cjs.map +1 -0
- package/dist/components/features/conversation-panel/edit-conversation-tags-modal.d.ts +22 -0
- package/dist/components/features/conversation-panel/edit-conversation-tags-modal.js +153 -0
- package/dist/components/features/conversation-panel/edit-conversation-tags-modal.js.map +1 -0
- package/dist/components/features/conversation-panel/hide-older-conversations-row.cjs +2 -0
- package/dist/components/features/conversation-panel/hide-older-conversations-row.cjs.map +1 -0
- package/dist/components/features/conversation-panel/hide-older-conversations-row.d.ts +6 -0
- package/dist/components/features/conversation-panel/hide-older-conversations-row.js +65 -0
- package/dist/components/features/conversation-panel/hide-older-conversations-row.js.map +1 -0
- package/dist/components/features/conversation-panel/menu-row.cjs +1 -1
- package/dist/components/features/conversation-panel/menu-row.cjs.map +1 -1
- package/dist/components/features/conversation-panel/menu-row.d.ts +11 -1
- package/dist/components/features/conversation-panel/menu-row.js +27 -16
- package/dist/components/features/conversation-panel/menu-row.js.map +1 -1
- package/dist/components/features/home/featured-automations/automation-health-indicator.cjs +1 -1
- package/dist/components/features/home/featured-automations/automation-health-indicator.cjs.map +1 -1
- package/dist/components/features/home/featured-automations/automation-health-indicator.js +1 -1
- package/dist/components/features/home/featured-automations/automation-health-indicator.js.map +1 -1
- package/dist/components/features/home/featured-automations/automation-run-activity-metrics.cjs +1 -1
- package/dist/components/features/home/featured-automations/automation-run-activity-metrics.cjs.map +1 -1
- package/dist/components/features/home/featured-automations/automation-run-activity-metrics.d.ts +4 -3
- package/dist/components/features/home/featured-automations/automation-run-activity-metrics.js +22 -25
- package/dist/components/features/home/featured-automations/automation-run-activity-metrics.js.map +1 -1
- package/dist/components/features/home/featured-automations/automation-run-activity-sparkline.cjs +1 -1
- package/dist/components/features/home/featured-automations/automation-run-activity-sparkline.cjs.map +1 -1
- package/dist/components/features/home/featured-automations/automation-run-activity-sparkline.d.ts +1 -1
- package/dist/components/features/home/featured-automations/automation-run-activity-sparkline.js +15 -15
- package/dist/components/features/home/featured-automations/automation-run-activity-sparkline.js.map +1 -1
- package/dist/components/features/home/featured-automations/automation-run-health.cjs +1 -1
- package/dist/components/features/home/featured-automations/automation-run-health.cjs.map +1 -1
- package/dist/components/features/home/featured-automations/automation-run-health.d.ts +5 -3
- package/dist/components/features/home/featured-automations/automation-run-health.js +30 -23
- package/dist/components/features/home/featured-automations/automation-run-health.js.map +1 -1
- package/dist/components/features/home/featured-automations/home-automation-activity.d.ts +3 -2
- package/dist/components/features/home/featured-automations/home-automation-run-tooltip.cjs +1 -1
- package/dist/components/features/home/featured-automations/home-automation-run-tooltip.cjs.map +1 -1
- package/dist/components/features/home/featured-automations/home-automation-run-tooltip.d.ts +1 -1
- package/dist/components/features/home/featured-automations/home-automation-run-tooltip.js +42 -42
- package/dist/components/features/home/featured-automations/home-automation-run-tooltip.js.map +1 -1
- package/dist/components/features/manifest/manifest-form-field.d.ts +3 -1
- package/dist/components/features/manifest/manifest-review-step.d.ts +3 -1
- package/dist/components/features/settings/cloud-settings-link.cjs +1 -1
- package/dist/components/features/settings/cloud-settings-link.cjs.map +1 -1
- package/dist/components/features/settings/cloud-settings-link.js +5 -3
- package/dist/components/features/settings/cloud-settings-link.js.map +1 -1
- package/dist/components/features/settings/integrations-settings-link.cjs +1 -1
- package/dist/components/features/settings/integrations-settings-link.cjs.map +1 -1
- package/dist/components/features/settings/integrations-settings-link.d.ts +3 -1
- package/dist/components/features/settings/integrations-settings-link.js +20 -17
- package/dist/components/features/settings/integrations-settings-link.js.map +1 -1
- package/dist/components/features/settings/llm-profiles/llm-settings-local-view.cjs +1 -1
- package/dist/components/features/settings/llm-profiles/llm-settings-local-view.js +1 -1
- package/dist/components/features/settings/llm-profiles/profiles-body.d.ts +2 -2
- package/dist/components/features/settings/sdk-settings/sdk-section-page.cjs +1 -1
- package/dist/components/features/settings/sdk-settings/sdk-section-page.cjs.map +1 -1
- package/dist/components/features/settings/sdk-settings/sdk-section-page.d.ts +8 -7
- package/dist/components/features/settings/sdk-settings/sdk-section-page.js +172 -142
- package/dist/components/features/settings/sdk-settings/sdk-section-page.js.map +1 -1
- package/dist/components/features/settings/secrets-settings/secret-form.cjs +1 -1
- package/dist/components/features/settings/secrets-settings/secret-form.cjs.map +1 -1
- package/dist/components/features/settings/secrets-settings/secret-form.js +38 -25
- package/dist/components/features/settings/secrets-settings/secret-form.js.map +1 -1
- package/dist/components/features/settings/settings-dropdown-input.cjs +1 -1
- package/dist/components/features/settings/settings-dropdown-input.js +6 -6
- package/dist/components/features/sidebar/sidebar-rail-body.cjs +1 -1
- package/dist/components/features/sidebar/sidebar-rail-body.cjs.map +1 -1
- package/dist/components/features/sidebar/sidebar-rail-body.d.ts +3 -1
- package/dist/components/features/sidebar/sidebar-rail-body.js +156 -139
- package/dist/components/features/sidebar/sidebar-rail-body.js.map +1 -1
- package/dist/components/features/sidebar/sidebar.cjs +1 -1
- package/dist/components/features/sidebar/sidebar.cjs.map +1 -1
- package/dist/components/features/sidebar/sidebar.js +44 -43
- package/dist/components/features/sidebar/sidebar.js.map +1 -1
- package/dist/components/features/sidebar/use-sidebar-onboarding-checklist.cjs +1 -1
- package/dist/components/features/sidebar/use-sidebar-onboarding-checklist.js +3 -3
- package/dist/components/features/skills/extensions-navigation.cjs +1 -1
- package/dist/components/features/skills/extensions-navigation.cjs.map +1 -1
- package/dist/components/features/skills/extensions-navigation.js +3 -3
- package/dist/components/features/skills/extensions-navigation.js.map +1 -1
- package/dist/components/shared/buttons/styled-tooltip.cjs +1 -1
- package/dist/components/shared/buttons/styled-tooltip.cjs.map +1 -1
- package/dist/components/shared/buttons/styled-tooltip.d.ts +2 -1
- package/dist/components/shared/buttons/styled-tooltip.js +2 -1
- package/dist/components/shared/buttons/styled-tooltip.js.map +1 -1
- package/dist/components/shared/filters/enum-filter-dropdown.cjs +1 -1
- package/dist/components/shared/filters/enum-filter-dropdown.cjs.map +1 -1
- package/dist/components/shared/filters/enum-filter-dropdown.d.ts +5 -1
- package/dist/components/shared/filters/enum-filter-dropdown.js +14 -14
- package/dist/components/shared/filters/enum-filter-dropdown.js.map +1 -1
- package/dist/components/shared/modals/confirmation-modal.cjs +1 -1
- package/dist/components/shared/modals/confirmation-modal.cjs.map +1 -1
- package/dist/components/shared/modals/confirmation-modal.d.ts +2 -1
- package/dist/components/shared/modals/confirmation-modal.js +8 -8
- package/dist/components/shared/modals/confirmation-modal.js.map +1 -1
- package/dist/components/shared/modals/settings/model-selector.cjs +1 -1
- package/dist/components/shared/modals/settings/model-selector.js +27 -27
- package/dist/config/defaults.cjs +1 -1
- package/dist/config/defaults.cjs.map +1 -1
- package/dist/config/defaults.js +3 -3
- package/dist/config/defaults.js.map +1 -1
- package/dist/constants/settings-nav.d.ts +8 -0
- package/dist/contexts/conversation-websocket-context.cjs +3 -3
- package/dist/contexts/conversation-websocket-context.cjs.map +1 -1
- package/dist/contexts/conversation-websocket-context.d.ts +5 -0
- package/dist/contexts/conversation-websocket-context.js +194 -198
- package/dist/contexts/conversation-websocket-context.js.map +1 -1
- package/dist/contexts/websocket-provider-wrapper.cjs +1 -1
- package/dist/contexts/websocket-provider-wrapper.cjs.map +1 -1
- package/dist/contexts/websocket-provider-wrapper.js +31 -15
- package/dist/contexts/websocket-provider-wrapper.js.map +1 -1
- package/dist/hooks/chat/record-model-switch-message.cjs +1 -1
- package/dist/hooks/chat/record-model-switch-message.cjs.map +1 -1
- package/dist/hooks/chat/record-model-switch-message.d.ts +31 -0
- package/dist/hooks/chat/record-model-switch-message.js +25 -12
- package/dist/hooks/chat/record-model-switch-message.js.map +1 -1
- package/dist/hooks/chat/use-plan-mode-interceptor.cjs +2 -0
- package/dist/hooks/chat/use-plan-mode-interceptor.cjs.map +1 -0
- package/dist/hooks/chat/use-plan-mode-interceptor.d.ts +16 -0
- package/dist/hooks/chat/use-plan-mode-interceptor.js +43 -0
- package/dist/hooks/chat/use-plan-mode-interceptor.js.map +1 -0
- package/dist/hooks/mutation/use-create-conversation.cjs +1 -1
- package/dist/hooks/mutation/use-create-conversation.cjs.map +1 -1
- package/dist/hooks/mutation/use-create-conversation.js +5 -1
- package/dist/hooks/mutation/use-create-conversation.js.map +1 -1
- package/dist/hooks/mutation/use-read-conversation-file.cjs +1 -1
- package/dist/hooks/mutation/use-read-conversation-file.cjs.map +1 -1
- package/dist/hooks/mutation/use-read-conversation-file.js +11 -4
- package/dist/hooks/mutation/use-read-conversation-file.js.map +1 -1
- package/dist/hooks/mutation/use-save-settings.cjs +1 -1
- package/dist/hooks/mutation/use-save-settings.js +3 -3
- package/dist/hooks/mutation/use-switch-llm-profile.cjs +1 -1
- package/dist/hooks/mutation/use-switch-llm-profile.cjs.map +1 -1
- package/dist/hooks/mutation/use-switch-llm-profile.js +13 -25
- package/dist/hooks/mutation/use-switch-llm-profile.js.map +1 -1
- package/dist/hooks/mutation/use-update-conversation-tags.cjs +2 -0
- package/dist/hooks/mutation/use-update-conversation-tags.cjs.map +1 -0
- package/dist/hooks/mutation/use-update-conversation-tags.d.ts +8 -0
- package/dist/hooks/mutation/use-update-conversation-tags.js +45 -0
- package/dist/hooks/mutation/use-update-conversation-tags.js.map +1 -0
- package/dist/hooks/query/query-keys.cjs +1 -1
- package/dist/hooks/query/query-keys.cjs.map +1 -1
- package/dist/hooks/query/query-keys.d.ts +6 -0
- package/dist/hooks/query/query-keys.js +2 -2
- package/dist/hooks/query/query-keys.js.map +1 -1
- package/dist/hooks/query/use-automation-sdk-version.cjs +1 -1
- package/dist/hooks/query/use-automation-sdk-version.cjs.map +1 -1
- package/dist/hooks/query/use-automation-sdk-version.d.ts +0 -1
- package/dist/hooks/query/use-automation-sdk-version.js +30 -55
- package/dist/hooks/query/use-automation-sdk-version.js.map +1 -1
- package/dist/hooks/query/use-conversation-skills.cjs +1 -1
- package/dist/hooks/query/use-conversation-skills.cjs.map +1 -1
- package/dist/hooks/query/use-conversation-skills.d.ts +14 -5
- package/dist/hooks/query/use-conversation-skills.js +20 -4
- package/dist/hooks/query/use-conversation-skills.js.map +1 -1
- package/dist/hooks/query/use-provider-connections.cjs +1 -1
- package/dist/hooks/query/use-provider-connections.cjs.map +1 -1
- package/dist/hooks/query/use-provider-connections.d.ts +6 -3
- package/dist/hooks/query/use-provider-connections.js +7 -3
- package/dist/hooks/query/use-provider-connections.js.map +1 -1
- package/dist/hooks/query/use-sub-conversations.cjs +1 -1
- package/dist/hooks/query/use-sub-conversations.cjs.map +1 -1
- package/dist/hooks/query/use-sub-conversations.js +16 -16
- package/dist/hooks/query/use-sub-conversations.js.map +1 -1
- package/dist/hooks/use-acp-model-context.cjs +1 -1
- package/dist/hooks/use-acp-model-context.js +3 -3
- package/dist/hooks/use-agent-state.cjs +1 -1
- package/dist/hooks/use-agent-state.cjs.map +1 -1
- package/dist/hooks/use-agent-state.d.ts +17 -1
- package/dist/hooks/use-agent-state.js +19 -9
- package/dist/hooks/use-agent-state.js.map +1 -1
- package/dist/hooks/use-automation-permissions.cjs +2 -0
- package/dist/hooks/use-automation-permissions.cjs.map +1 -0
- package/dist/hooks/use-automation-permissions.d.ts +43 -0
- package/dist/hooks/use-automation-permissions.js +52 -0
- package/dist/hooks/use-automation-permissions.js.map +1 -0
- package/dist/hooks/use-chat-input-model-state.cjs +1 -1
- package/dist/hooks/use-chat-input-model-state.js +3 -3
- package/dist/hooks/use-handle-plan-click.cjs +1 -1
- package/dist/hooks/use-handle-plan-click.cjs.map +1 -1
- package/dist/hooks/use-handle-plan-click.d.ts +3 -1
- package/dist/hooks/use-handle-plan-click.js +85 -26
- package/dist/hooks/use-handle-plan-click.js.map +1 -1
- package/dist/hooks/use-home-automation-actions.cjs +1 -1
- package/dist/hooks/use-home-automation-actions.cjs.map +1 -1
- package/dist/hooks/use-home-automation-actions.js +1 -0
- package/dist/hooks/use-home-automation-actions.js.map +1 -1
- package/dist/hooks/use-llm-configured.cjs +1 -1
- package/dist/hooks/use-llm-configured.js +3 -3
- package/dist/hooks/use-manifest-preflight.d.ts +1 -1
- package/dist/hooks/use-unified-websocket-status.cjs +1 -1
- package/dist/hooks/use-unified-websocket-status.cjs.map +1 -1
- package/dist/hooks/use-unified-websocket-status.d.ts +7 -0
- package/dist/hooks/use-unified-websocket-status.js +5 -1
- package/dist/hooks/use-unified-websocket-status.js.map +1 -1
- package/dist/i18n/declaration.cjs +1 -1
- package/dist/i18n/declaration.cjs.map +1 -1
- package/dist/i18n/declaration.d.ts +65 -12
- package/dist/i18n/declaration.js +1 -1
- package/dist/i18n/declaration.js.map +1 -1
- package/dist/i18n/translation.cjs +2 -2
- package/dist/i18n/translation.cjs.map +1 -1
- package/dist/i18n/translation.js +1196 -295
- package/dist/i18n/translation.js.map +1 -1
- package/dist/locales/ar/openhands.json +73 -20
- package/dist/locales/ca/openhands.json +73 -20
- package/dist/locales/de/openhands.json +73 -20
- package/dist/locales/en/openhands.json +73 -20
- package/dist/locales/es/openhands.json +73 -20
- package/dist/locales/fr/openhands.json +73 -20
- package/dist/locales/it/openhands.json +73 -20
- package/dist/locales/ja/openhands.json +73 -20
- package/dist/locales/ko-KR/openhands.json +73 -20
- package/dist/locales/no/openhands.json +73 -20
- package/dist/locales/pt/openhands.json +73 -20
- package/dist/locales/tr/openhands.json +73 -20
- package/dist/locales/uk/openhands.json +73 -20
- package/dist/locales/zh-CN/openhands.json +73 -20
- package/dist/locales/zh-TW/openhands.json +73 -20
- package/dist/manifests/automation-insights.cjs.map +1 -1
- package/dist/manifests/automation-insights.d.ts +10 -2
- package/dist/manifests/automation-insights.js.map +1 -1
- package/dist/manifests/automation-setup.cjs +1 -1
- package/dist/manifests/automation-setup.cjs.map +1 -1
- package/dist/manifests/automation-setup.d.ts +9 -16
- package/dist/manifests/automation-setup.js +17 -7
- package/dist/manifests/automation-setup.js.map +1 -1
- package/dist/manifests/manifest-actions.d.ts +1 -1
- package/dist/manifests/manifest-capabilities.d.ts +3 -1
- package/dist/manifests/manifest-local-validation.cjs +2 -0
- package/dist/manifests/manifest-local-validation.cjs.map +1 -0
- package/dist/manifests/manifest-local-validation.d.ts +15 -11
- package/dist/manifests/manifest-local-validation.js +8 -0
- package/dist/manifests/manifest-local-validation.js.map +1 -0
- package/dist/manifests/manifest-template.cjs +2 -0
- package/dist/manifests/manifest-template.cjs.map +1 -0
- package/dist/manifests/manifest-template.d.ts +4 -4
- package/dist/manifests/manifest-template.js +12 -0
- package/dist/manifests/manifest-template.js.map +1 -0
- package/dist/manifests/manifest-validation.cjs +1 -1
- package/dist/manifests/manifest-validation.cjs.map +1 -1
- package/dist/manifests/manifest-validation.js +106 -28
- package/dist/manifests/manifest-validation.js.map +1 -1
- package/dist/manifests/types.cjs.map +1 -1
- package/dist/manifests/types.d.ts +39 -4
- package/dist/manifests/types.js.map +1 -1
- package/dist/node_modules/@openhands/extensions/automations/catalog/custom-automation/manifest.cjs +8 -0
- package/dist/node_modules/@openhands/extensions/automations/catalog/custom-automation/manifest.cjs.map +1 -0
- package/dist/node_modules/@openhands/extensions/automations/catalog/custom-automation/manifest.js +221 -0
- package/dist/node_modules/@openhands/extensions/automations/catalog/custom-automation/manifest.js.map +1 -0
- package/dist/node_modules/@openhands/extensions/automations/catalog/github-issue-to-pr/manifest.cjs +1 -1
- package/dist/node_modules/@openhands/extensions/automations/catalog/github-issue-to-pr/manifest.cjs.map +1 -1
- package/dist/node_modules/@openhands/extensions/automations/catalog/github-issue-to-pr/manifest.js +1 -1
- package/dist/node_modules/@openhands/extensions/automations/catalog/github-issue-to-pr/manifest.js.map +1 -1
- package/dist/node_modules/@openhands/extensions/automations/catalog/github-pr-reviewer/manifest.cjs +1 -1
- package/dist/node_modules/@openhands/extensions/automations/catalog/github-pr-reviewer/manifest.cjs.map +1 -1
- package/dist/node_modules/@openhands/extensions/automations/catalog/github-pr-reviewer/manifest.js +1 -1
- package/dist/node_modules/@openhands/extensions/automations/catalog/github-pr-reviewer/manifest.js.map +1 -1
- package/dist/node_modules/@openhands/extensions/automations/catalog/qa-changes/manifest.cjs +1 -1
- package/dist/node_modules/@openhands/extensions/automations/catalog/qa-changes/manifest.cjs.map +1 -1
- package/dist/node_modules/@openhands/extensions/automations/catalog/qa-changes/manifest.js +1 -1
- package/dist/node_modules/@openhands/extensions/automations/catalog/qa-changes/manifest.js.map +1 -1
- package/dist/node_modules/@openhands/extensions/automations/catalog-index.cjs +1 -1
- package/dist/node_modules/@openhands/extensions/automations/catalog-index.cjs.map +1 -1
- package/dist/node_modules/@openhands/extensions/automations/catalog-index.js +22 -20
- package/dist/node_modules/@openhands/extensions/automations/catalog-index.js.map +1 -1
- package/dist/node_modules/@openhands/extensions/automations/interface.cjs +1 -1
- package/dist/node_modules/@openhands/extensions/automations/interface.cjs.map +1 -1
- package/dist/node_modules/@openhands/extensions/automations/interface.js +2 -1
- package/dist/node_modules/@openhands/extensions/automations/interface.js.map +1 -1
- package/dist/node_modules/@openhands/extensions/integrations/catalog/gitlab.cjs +1 -1
- package/dist/node_modules/@openhands/extensions/integrations/catalog/gitlab.cjs.map +1 -1
- package/dist/node_modules/@openhands/extensions/integrations/catalog/gitlab.js +24 -5
- package/dist/node_modules/@openhands/extensions/integrations/catalog/gitlab.js.map +1 -1
- package/dist/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.cjs +1 -1
- package/dist/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js +1 -1
- package/dist/node_modules/lucide-react/dist/esm/icons/circle-question-mark.cjs +2 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/circle-question-mark.cjs.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/circle-question-mark.js +21 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/circle-question-mark.js.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/shrink.cjs +2 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/shrink.cjs.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/shrink.js +23 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/shrink.js.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/sliders-horizontal.cjs +2 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/sliders-horizontal.cjs.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/sliders-horizontal.js +43 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/sliders-horizontal.js.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/tags.cjs +2 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/tags.cjs.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/tags.js +22 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/tags.js.map +1 -0
- package/dist/package.cjs +1 -1
- package/dist/package.cjs.map +1 -1
- package/dist/package.js +4 -4
- package/dist/package.js.map +1 -1
- package/dist/routes/agent-settings.d.ts +2 -0
- package/dist/routes/app-settings.cjs +1 -1
- package/dist/routes/app-settings.js +5 -5
- package/dist/routes/llm-settings.cjs +1 -1
- package/dist/routes/llm-settings.cjs.map +1 -1
- package/dist/routes/llm-settings.d.ts +3 -1
- package/dist/routes/llm-settings.js +88 -87
- package/dist/routes/llm-settings.js.map +1 -1
- package/dist/routes/planner-tab.cjs +1 -1
- package/dist/routes/planner-tab.cjs.map +1 -1
- package/dist/routes/planner-tab.js +44 -28
- package/dist/routes/planner-tab.js.map +1 -1
- package/dist/services/telemetry.cjs +1 -1
- package/dist/services/telemetry.cjs.map +1 -1
- package/dist/services/telemetry.d.ts +5 -0
- package/dist/services/telemetry.js +122 -115
- package/dist/services/telemetry.js.map +1 -1
- package/dist/stores/conversation-panel-preferences-store.cjs +1 -1
- package/dist/stores/conversation-panel-preferences-store.cjs.map +1 -1
- package/dist/stores/conversation-panel-preferences-store.d.ts +32 -5
- package/dist/stores/conversation-panel-preferences-store.js +29 -12
- package/dist/stores/conversation-panel-preferences-store.js.map +1 -1
- package/dist/stores/conversation-state-store.cjs +1 -1
- package/dist/stores/conversation-state-store.cjs.map +1 -1
- package/dist/stores/conversation-state-store.d.ts +9 -3
- package/dist/stores/conversation-state-store.js +6 -3
- package/dist/stores/conversation-state-store.js.map +1 -1
- package/dist/stores/conversation-store.cjs +1 -1
- package/dist/stores/conversation-store.cjs.map +1 -1
- package/dist/stores/conversation-store.d.ts +2 -0
- package/dist/stores/conversation-store.js +3 -0
- package/dist/stores/conversation-store.js.map +1 -1
- package/dist/stores/model-store.cjs +1 -1
- package/dist/stores/model-store.cjs.map +1 -1
- package/dist/stores/model-store.d.ts +7 -0
- package/dist/stores/model-store.js +4 -0
- package/dist/stores/model-store.js.map +1 -1
- package/dist/types/automation.cjs.map +1 -1
- package/dist/types/automation.d.ts +46 -1
- package/dist/types/automation.js.map +1 -1
- package/dist/ui/context-menu.d.ts +1 -1
- package/dist/ui/toggle-switch.cjs +1 -1
- package/dist/ui/toggle-switch.cjs.map +1 -1
- package/dist/ui/toggle-switch.d.ts +4 -1
- package/dist/ui/toggle-switch.js +4 -3
- package/dist/ui/toggle-switch.js.map +1 -1
- package/dist/utils/automation-catalog.cjs +1 -1
- package/dist/utils/automation-catalog.cjs.map +1 -1
- package/dist/utils/automation-catalog.d.ts +12 -1
- package/dist/utils/automation-catalog.js +42 -12
- package/dist/utils/automation-catalog.js.map +1 -1
- package/dist/utils/automation-run-display.cjs +2 -0
- package/dist/utils/automation-run-display.cjs.map +1 -0
- package/dist/utils/automation-run-display.d.ts +20 -0
- package/dist/utils/automation-run-display.js +85 -0
- package/dist/utils/automation-run-display.js.map +1 -0
- package/dist/utils/automation-schedule.cjs +1 -1
- package/dist/utils/automation-schedule.cjs.map +1 -1
- package/dist/utils/automation-schedule.d.ts +16 -0
- package/dist/utils/automation-schedule.js +2 -0
- package/dist/utils/automation-schedule.js.map +1 -1
- package/dist/utils/constants.cjs +1 -1
- package/dist/utils/constants.cjs.map +1 -1
- package/dist/utils/constants.d.ts +4 -0
- package/dist/utils/constants.js +23 -3
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/format-event-timestamp.cjs +2 -0
- package/dist/utils/format-event-timestamp.cjs.map +1 -0
- package/dist/utils/format-event-timestamp.d.ts +1 -0
- package/dist/utils/format-event-timestamp.js +13 -0
- package/dist/utils/format-event-timestamp.js.map +1 -0
- package/dist/utils/git-diff-stats.cjs +2 -2
- package/dist/utils/git-diff-stats.cjs.map +1 -1
- package/dist/utils/git-diff-stats.d.ts +4 -0
- package/dist/utils/git-diff-stats.js +12 -2
- package/dist/utils/git-diff-stats.js.map +1 -1
- package/dist/utils/plan-file.cjs +4 -0
- package/dist/utils/plan-file.cjs.map +1 -0
- package/dist/utils/plan-file.d.ts +18 -0
- package/dist/utils/plan-file.js +58 -0
- package/dist/utils/plan-file.js.map +1 -0
- package/dist/utils/recommended-automation-rail.cjs +1 -1
- package/dist/utils/recommended-automation-rail.cjs.map +1 -1
- package/dist/utils/recommended-automation-rail.js +1 -0
- package/dist/utils/recommended-automation-rail.js.map +1 -1
- package/dist/utils/scroll-fade-state.cjs +1 -1
- package/dist/utils/scroll-fade-state.cjs.map +1 -1
- package/dist/utils/scroll-fade-state.d.ts +6 -0
- package/dist/utils/scroll-fade-state.js +8 -1
- package/dist/utils/scroll-fade-state.js.map +1 -1
- package/dist/utils/sdk-settings-schema.cjs +1 -1
- package/dist/utils/sdk-settings-schema.cjs.map +1 -1
- package/dist/utils/sdk-settings-schema.d.ts +1 -0
- package/dist/utils/sdk-settings-schema.js +25 -22
- package/dist/utils/sdk-settings-schema.js.map +1 -1
- package/dist/utils/utils.cjs +1 -1
- package/dist/utils/utils.cjs.map +1 -1
- package/dist/utils/utils.js +11 -11
- package/dist/utils/utils.js.map +1 -1
- package/package.json +4 -4
- package/scripts/check-sdk-version-sync.mjs +6 -6
- package/scripts/dev-safe.mjs +1 -1
- package/scripts/dev-with-automation.mjs +1 -1
- package/scripts/static-server.mjs +33 -1
- package/build/assets/active-backend-context-CrHTlgvN.js +0 -2
- package/build/assets/agent-profiles-settings-Dzgb7G9z.js +0 -1
- package/build/assets/agent-server-conversation-service.api-tnqNjbpE.js +0 -86
- package/build/assets/agent-settings-DLtsqaOs.js +0 -2
- package/build/assets/agent-settings-fFvR-XC2.js +0 -1
- package/build/assets/api-key-entry-screen-qTzrmJcr.js +0 -1
- package/build/assets/automation-detail-BmwlCUAB.js +0 -3
- package/build/assets/automation-git-sync-CprN5DcU.js +0 -1
- package/build/assets/automation-setup-route-C40kXjMY.js +0 -1
- package/build/assets/automations-list-DuNcRawl.js +0 -1
- package/build/assets/backend-form-modal-lhGwzXFI.js +0 -1
- package/build/assets/bot-dMYHVfYM.js +0 -1
- package/build/assets/canvas-extensions-8A-XhpgP.js +0 -1
- package/build/assets/check-CS0vmpQQ.js +0 -1
- package/build/assets/chevron-down-e1QBpjVy.js +0 -1
- package/build/assets/chevron-right-ZG4R5G4Q.js +0 -1
- package/build/assets/circle-check-DzWLNsbE.js +0 -1
- package/build/assets/command-menu--UIlGL72.js +0 -1
- package/build/assets/commits-tab-AnZwLvh-.js +0 -1
- package/build/assets/confirmation-modal-Dv1ktp8K.js +0 -1
- package/build/assets/conversation-BbGrAO4Z.js +0 -93
- package/build/assets/conversation-mYT6NAoj.js +0 -1
- package/build/assets/conversation-panel-mYT6NAoj.js +0 -1
- package/build/assets/edit-automation-modal-DadGbJ2u.js +0 -1
- package/build/assets/empty-state-BoGVDecT.js +0 -1
- package/build/assets/enum-filter-dropdown-Bgca129f.js +0 -1
- package/build/assets/extensions-hub-CcDqq_vR.js +0 -1
- package/build/assets/extensions-navigation-Cr4yzYk4.js +0 -1
- package/build/assets/external-link-DKUlnCQV.js +0 -1
- package/build/assets/files-tab-D4o2VyE_.js +0 -1
- package/build/assets/get-git-path-ChKDVHZp.js +0 -1
- package/build/assets/git-repo-dropdown-BkqzkMmM.js +0 -1
- package/build/assets/home-ChpSuepX.js +0 -1
- package/build/assets/home-DIsB440S.js +0 -1
- package/build/assets/home-automation-run-tooltip-BVNDWjAH.js +0 -1
- package/build/assets/home-store-CY9Ej0ux.js +0 -1
- package/build/assets/index-home-BdlrFIif.js +0 -1
- package/build/assets/info-B6RQawoW.js +0 -1
- package/build/assets/install-server-modal-Dpy89p4g.js +0 -1
- package/build/assets/kebab-menu-item-content-D8hSNGmm.js +0 -1
- package/build/assets/list-filter-DsEK7NFC.js +0 -1
- package/build/assets/llm-not-configured-banner-CIFABh3K.js +0 -27
- package/build/assets/llm-settings-B9juXhIa.js +0 -1
- package/build/assets/llm-settings-De1d5dfr.js +0 -1
- package/build/assets/llm-subscription-service-BpJ0ouQo.js +0 -1
- package/build/assets/loader-circle-CC1au6hc.js +0 -1
- package/build/assets/manage-backends-modal-BrPV3oB-.js +0 -1
- package/build/assets/manifest-80fb1c29.js +0 -1
- package/build/assets/markdown-renderer-BzTlxI8x.js +0 -8
- package/build/assets/mcp-page-BEBYqH_K.js +0 -9
- package/build/assets/messages-DKV9cRc2.js +0 -41
- package/build/assets/onboarding-DV_OXoeP.js +0 -1
- package/build/assets/onboarding-modal-rkyBYkeQ.js +0 -1
- package/build/assets/organization-service.api-DrYNkEN_.js +0 -1
- package/build/assets/pencil-47jAZH_V.js +0 -1
- package/build/assets/pin-By41Vl5J.js +0 -1
- package/build/assets/planner-tab-C9uZmhme.js +0 -1
- package/build/assets/plug-CG5HFcNG.js +0 -1
- package/build/assets/plugins-management-service-BMJgC3Ga.js +0 -1
- package/build/assets/plus-C7Slq-0I.js +0 -1
- package/build/assets/profiles-service.api-ChoKrL0l.js +0 -1
- package/build/assets/proxy-9Fe1v5na.js +0 -1
- package/build/assets/recommended-automations-launcher-6ABrI9Cy.js +0 -1
- package/build/assets/refresh-cw-3q3Frrii.js +0 -1
- package/build/assets/root-Dc8YmC9F.js +0 -2
- package/build/assets/root-VDaypvK4.css +0 -1
- package/build/assets/root-layout-CXkhSPYh.js +0 -2
- package/build/assets/schema-field-D-y9-LDb.js +0 -1
- package/build/assets/scroll-fade-state-Dw-i4SGq.js +0 -1
- package/build/assets/sdk-section-page-BlFiizlG.js +0 -1
- package/build/assets/secret-form-BB7the8W.js +0 -1
- package/build/assets/secrets-service-ByZVENbp.js +0 -1
- package/build/assets/settings-C0EWuBtA.js +0 -1
- package/build/assets/settings-index-5w5UW2K3.js +0 -1
- package/build/assets/settings-service.api-DJ1r62Zr.js +0 -1
- package/build/assets/shared-conversation-CA5ISEDy.js +0 -1
- package/build/assets/shield-check-D2aCRd16.js +0 -1
- package/build/assets/sidebar-mobile-menu-toggle-C_fb0Jki.js +0 -1
- package/build/assets/skill-scope-B42H-XOc.js +0 -1
- package/build/assets/skills-plugins-tgF8EiXC.js +0 -1
- package/build/assets/skills-settings-BgJ9PaOe.js +0 -1
- package/build/assets/styled-tooltip-C4jf5Fv9.js +0 -1
- package/build/assets/toggle-switch-ChOLXv2n.js +0 -1
- package/build/assets/trash-2-D7K26eDO.js +0 -1
- package/build/assets/trash-D-HYiVYM.js +0 -1
- package/build/assets/triangle-alert-CtdXWHUI.js +0 -1
- package/build/assets/usage-tab-CVDffLPt.js +0 -1
- package/build/assets/use-acp-credential-form-XIQmpX6M.js +0 -1
- package/build/assets/use-activate-agent-profile-BLfmvDiG.js +0 -1
- package/build/assets/use-agent-profiles-Bd9Bvvp1.js +0 -1
- package/build/assets/use-canvas-extensions-Bi693ujJ.js +0 -1
- package/build/assets/use-config-A9QovgXO.js +0 -1
- package/build/assets/use-create-conversation-Dx3l7mFZ.js +0 -1
- package/build/assets/use-create-secret-B9cUFWRG.js +0 -1
- package/build/assets/use-has-permission-B3C8rQmR.js +0 -1
- package/build/assets/use-is-authed-nTBcM7dE.js +0 -1
- package/build/assets/use-llm-configured-BV9nZ1_j.js +0 -1
- package/build/assets/use-llm-profiles-BO-P4Oyo.js +0 -1
- package/build/assets/use-manifest-capabilities-Cw_flwEz.js +0 -1
- package/build/assets/use-onboarding-completion-D8U7k4Qb.js +0 -1
- package/build/assets/use-pinned-home-route-CckOZrmb.js +0 -1
- package/build/assets/use-plugins-marketplace-7i94Zu3Z.js +0 -1
- package/build/assets/use-save-agent-profile-BUjSsDXK.js +0 -1
- package/build/assets/use-settings-DSXLbvBl.js +0 -1
- package/build/assets/use-settings-nav-items-CwRw-CmX.js +0 -16
- package/build/assets/use-tracking-D53y3TH9.js +0 -3
- package/build/assets/utils-CN5ZfZy1.js +0 -1
- package/build/assets/vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~czjee3a3-DqZDXfg2.js +0 -186
- package/build/assets/vendor~root-layout-C9Ju9DQg.js +0 -1
- package/build/assets/vendor~root-layout~index-home~home~conversation-panel~conversation~mcp~automations-list~aut~ibfbmr9f-cDuKr-WD.js +0 -1
- package/build/assets/vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~iyia17eu-Chf7vmKB.js +0 -1
- package/build/assets/x-DJdhbdmt.js +0 -1
- package/dist/components/features/conversation-panel/conversation-panel-filter-menu.cjs +0 -2
- package/dist/components/features/conversation-panel/conversation-panel-filter-menu.cjs.map +0 -1
- package/dist/components/features/conversation-panel/conversation-panel-filter-menu.d.ts +0 -35
- package/dist/components/features/conversation-panel/conversation-panel-filter-menu.js +0 -247
- package/dist/components/features/conversation-panel/conversation-panel-filter-menu.js.map +0 -1
- package/dist/hooks/query/use-skills.cjs +0 -2
- package/dist/hooks/query/use-skills.cjs.map +0 -1
- package/dist/hooks/query/use-skills.d.ts +0 -7
- package/dist/hooks/query/use-skills.js +0 -13
- package/dist/hooks/query/use-skills.js.map +0 -1
- package/dist/hooks/use-has-permission.cjs +0 -2
- package/dist/hooks/use-has-permission.cjs.map +0 -1
- package/dist/hooks/use-has-permission.d.ts +0 -1
- package/dist/hooks/use-has-permission.js +0 -8
- package/dist/hooks/use-has-permission.js.map +0 -1
- package/dist/node_modules/lucide-react/dist/esm/icons/eye.cjs +0 -2
- package/dist/node_modules/lucide-react/dist/esm/icons/eye.cjs.map +0 -1
- package/dist/node_modules/lucide-react/dist/esm/icons/eye.js +0 -14
- package/dist/node_modules/lucide-react/dist/esm/icons/eye.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"automation-insights.js","names":[],"sources":["../../src/manifests/automation-insights.ts"],"sourcesContent":["/**\n * The dashboard's automation knowledge, and the only module beside\n * `automation-setup.ts` that has any.\n *\n * The interface manifest's dashboard surface names metrics, filter predicates,\n * and sort comparators from closed sets; the one implementation of each name\n * is here, over the automation service's list and runs responses, so the rest\n * of `src/manifests/` stays about composition rather than about automations.\n *\n * Summaries are derived from the newest page of runs (the sample the runs\n * hook fetches); `total` alone is the response's lifetime count. Success rate\n * and durations consider only COMPLETED and FAILED runs — a cancelled or\n * skipped run says nothing about either.\n */\n\nimport {\n AutomationRunStatus,\n type Automation,\n type AutomationRun,\n type AutomationRunsResponse,\n} from \"#/types/automation\";\nimport type {\n DashboardSortValue,\n DashboardStatusValue,\n DashboardTriggerValue,\n InterfaceListInsights,\n OverviewMetric,\n} from \"./types\";\n\nexport type AutomationHealth =\n | \"healthy\"\n | \"failing\"\n | \"running\"\n | \"disabled\"\n | \"never-run\"\n | \"unknown\";\n\n/** Which caption of the manifest's `insights.health` block names each state. */\nexport const HEALTH_LABEL_KEYS: Record<\n AutomationHealth,\n keyof InterfaceListInsights[\"health\"]\n> = {\n healthy: \"healthy\",\n failing: \"failing\",\n running: \"running\",\n disabled: \"disabled\",\n \"never-run\": \"neverRun\",\n unknown: \"checking\",\n};\n\nexport interface AutomationRunSummary {\n /** Lifetime run count, from the response — not the sample's length. */\n total: number;\n latestRun: AutomationRun | null;\n /** Newest-first sample used by the list sparkline (same page as the summary). */\n recentRuns: AutomationRun[];\n /** COMPLETED over COMPLETED+FAILED in the sample. Null with no terminal runs. */\n recentSuccessRate: number | null;\n /** Mean completed_at − started_at over the sample's terminal runs. */\n averageDurationMs: number | null;\n}\n\n/** One automation's summary alongside its query state. */\nexport interface RunSummaryState {\n summary: AutomationRunSummary | null;\n isLoading: boolean;\n isError: boolean;\n}\n\ntype Summaries = ReadonlyMap<string, RunSummaryState>;\n\nconst TERMINAL_STATUSES = new Set<AutomationRunStatus>([\n AutomationRunStatus.COMPLETED,\n AutomationRunStatus.FAILED,\n]);\n\nexport function summarizeAutomationRuns(\n response: AutomationRunsResponse,\n): AutomationRunSummary {\n const terminal = response.runs.filter((run) =>\n TERMINAL_STATUSES.has(run.status),\n );\n const completed = terminal.filter(\n (run) => run.status === AutomationRunStatus.COMPLETED,\n ).length;\n\n const durations = terminal.flatMap((run) => {\n if (!run.completed_at) return [];\n const ms =\n new Date(run.completed_at).getTime() - new Date(run.started_at).getTime();\n return Number.isFinite(ms) && ms >= 0 ? [ms] : [];\n });\n\n return {\n total: response.total,\n latestRun: response.runs[0] ?? null,\n recentRuns: response.runs,\n recentSuccessRate:\n terminal.length === 0 ? null : completed / terminal.length,\n averageDurationMs:\n durations.length === 0\n ? null\n : durations.reduce((sum, ms) => sum + ms, 0) / durations.length,\n };\n}\n\n/**\n * A disabled automation is disabled no matter its history; while the summary\n * is unsettled nothing is claimed; after that the latest run speaks.\n */\nexport function deriveAutomationHealth(\n automation: Automation,\n state: RunSummaryState | undefined,\n): AutomationHealth {\n if (!automation.enabled) return \"disabled\";\n if (!state || state.isError || state.isLoading || !state.summary) {\n return \"unknown\";\n }\n const latest = state.summary.latestRun;\n if (!latest) return \"never-run\";\n if (latest.status === AutomationRunStatus.FAILED) return \"failing\";\n if (\n latest.status === AutomationRunStatus.PENDING ||\n latest.status === AutomationRunStatus.RUNNING\n ) {\n return \"running\";\n }\n return \"healthy\";\n}\n\n/** \"—\" unknown, seconds under a minute, minutes under an hour, else \"1.5h\". */\nexport function formatCompactDuration(ms: number | null): string {\n if (ms === null || !Number.isFinite(ms)) return \"—\";\n if (ms < 60_000) return `${Math.max(1, Math.round(ms / 1000))}s`;\n if (ms < 3_600_000) return `${Math.max(1, Math.round(ms / 60_000))}m`;\n return `${(ms / 3_600_000).toFixed(1)}h`;\n}\n\n/** The list page's search predicate: name, prompt, repository, or model. */\nexport function matchesAutomationSearch(\n automation: Automation,\n query: string,\n): boolean {\n const q = query.toLowerCase();\n if (!q) return true;\n return (\n automation.name.toLowerCase().includes(q) ||\n (automation.prompt ?? \"\").toLowerCase().includes(q) ||\n automation.repository?.toLowerCase().includes(q) === true ||\n automation.model?.toLowerCase().includes(q) === true\n );\n}\n\nconst STATUS_PREDICATES: Record<\n Exclude<DashboardStatusValue, \"all\">,\n (automation: Automation, health: AutomationHealth) => boolean\n> = {\n active: (automation) => automation.enabled,\n failing: (_automation, health) => health === \"failing\",\n disabled: (automation) => !automation.enabled,\n};\n\nconst TRIGGER_PREDICATES: Record<\n Exclude<DashboardTriggerValue, \"all\">,\n (automation: Automation) => boolean\n> = {\n // The backend emits several scheduled-trigger aliases, so \"event\" is the\n // one exact kind and everything else is a schedule.\n event: (automation) => automation.trigger.type === \"event\",\n schedule: (automation) => automation.trigger.type !== \"event\",\n};\n\nfunction runCount(automation: Automation, byId: Summaries): number {\n return byId.get(automation.id)?.summary?.total ?? 0;\n}\n\nfunction lastRunTime(automation: Automation, byId: Summaries): number {\n const startedAt =\n byId.get(automation.id)?.summary?.latestRun?.started_at ??\n automation.last_triggered_at;\n if (!startedAt) return 0;\n const time = new Date(startedAt).getTime();\n return Number.isFinite(time) ? time : 0;\n}\n\nconst SORT_COMPARATORS: Record<\n DashboardSortValue,\n (a: Automation, b: Automation, byId: Summaries) => number\n> = {\n \"last-run\": (a, b, byId) => lastRunTime(b, byId) - lastRunTime(a, byId),\n runs: (a, b, byId) => runCount(b, byId) - runCount(a, byId),\n name: (a, b) => a.name.localeCompare(b.name),\n};\n\nexport interface DashboardViewState {\n search: string;\n status: DashboardStatusValue;\n trigger: DashboardTriggerValue;\n sort: DashboardSortValue;\n}\n\n/** The automations the dashboard shows, filtered and ordered. */\nexport function applyDashboardView(\n automations: readonly Automation[],\n view: DashboardViewState,\n byId: Summaries,\n): Automation[] {\n return automations\n .filter((automation) => {\n if (!matchesAutomationSearch(automation, view.search)) return false;\n if (\n view.status !== \"all\" &&\n !STATUS_PREDICATES[view.status](\n automation,\n deriveAutomationHealth(automation, byId.get(automation.id)),\n )\n ) {\n return false;\n }\n if (\n view.trigger !== \"all\" &&\n !TRIGGER_PREDICATES[view.trigger](automation)\n ) {\n return false;\n }\n return true;\n })\n .sort((a, b) => SORT_COMPARATORS[view.sort](a, b, byId));\n}\n\nexport interface OverviewTileValue {\n /** The formatted value the tile displays. */\n display: string;\n /** True when the metric's value is zero, which swaps in `zeroDetail`. */\n isZero: boolean;\n /** Values the tile's copy may substitute via `{{name}}`. */\n placeholderValues: Record<string, string | number>;\n}\n\nfunction loadedSummaries(\n automations: readonly Automation[],\n byId: Summaries,\n): AutomationRunSummary[] {\n return automations.flatMap((automation) => {\n const state = byId.get(automation.id);\n return state?.summary && !state.isLoading && !state.isError\n ? [state.summary]\n : [];\n });\n}\n\nconst TILE_COMPUTATIONS: Record<\n OverviewMetric,\n (automations: readonly Automation[], byId: Summaries) => OverviewTileValue\n> = {\n automations: (automations) => ({\n display: automations.length.toLocaleString(),\n isZero: automations.length === 0,\n placeholderValues: {\n active: automations.filter((automation) => automation.enabled).length,\n },\n }),\n \"needs-attention\": (automations, byId) => {\n const failing = automations.filter(\n (automation) =>\n deriveAutomationHealth(automation, byId.get(automation.id)) ===\n \"failing\",\n ).length;\n return {\n display: failing.toLocaleString(),\n isZero: failing === 0,\n placeholderValues: {},\n };\n },\n \"total-runs\": (automations, byId) => {\n const loaded = loadedSummaries(automations, byId);\n if (loaded.length === 0 && automations.length > 0) {\n return { display: \"—\", isZero: false, placeholderValues: {} };\n }\n const total = loaded.reduce((sum, summary) => sum + summary.total, 0);\n return {\n display: total.toLocaleString(),\n isZero: total === 0,\n placeholderValues: {},\n };\n },\n \"average-duration\": (automations, byId) => {\n const durations = loadedSummaries(automations, byId).flatMap((summary) =>\n summary.averageDurationMs === null ? [] : [summary.averageDurationMs],\n );\n const mean =\n durations.length === 0\n ? null\n : durations.reduce((sum, ms) => sum + ms, 0) / durations.length;\n return {\n display: formatCompactDuration(mean),\n isZero: false,\n placeholderValues: {},\n };\n },\n};\n\n/** The value behind one manifest-declared tile. */\nexport function computeOverviewTile(\n metric: OverviewMetric,\n automations: readonly Automation[],\n byId: Summaries,\n): OverviewTileValue {\n return TILE_COMPUTATIONS[metric](automations, byId);\n}\n"],"mappings":";AAuE0B,IAAI,IAAyB,CACrD,EAAoB,WACpB,EAAoB,MACtB,CAAC;AAyDD,SAAgB,EAAsB,GAA2B;CAI/D,OAHI,MAAO,QAAQ,CAAC,OAAO,SAAS,CAAE,IAAU,MAC5C,IAAK,MAAe,GAAG,KAAK,IAAI,GAAG,KAAK,MAAM,IAAK,GAAI,CAAC,EAAE,KAC1D,IAAK,OAAkB,GAAG,KAAK,IAAI,GAAG,KAAK,MAAM,IAAK,GAAM,CAAC,EAAE,KAC5D,IAAI,IAAK,MAAW,QAAQ,CAAC,EAAE;AACxC"}
|
|
1
|
+
{"version":3,"file":"automation-insights.js","names":[],"sources":["../../src/manifests/automation-insights.ts"],"sourcesContent":["/**\n * The dashboard's automation knowledge, and the only module beside\n * `automation-setup.ts` that has any.\n *\n * The interface manifest's dashboard surface names metrics, filter predicates,\n * and sort comparators from closed sets; the one implementation of each name\n * is here, over the automation service's list and runs responses, so the rest\n * of `src/manifests/` stays about composition rather than about automations.\n *\n * Summaries are derived from the newest page of runs (the sample the runs\n * hook fetches); `total` alone is the response's lifetime count. Success rate\n * and durations consider only terminal lifecycle runs; the success numerator\n * uses task-aware display status so completed-but-blocked work is not counted\n * as successful.\n */\n\nimport {\n AutomationRunStatus,\n type Automation,\n type AutomationRun,\n type AutomationRunsResponse,\n} from \"#/types/automation\";\nimport { getAutomationRunDisplay } from \"#/utils/automation-run-display\";\nimport type {\n DashboardSortValue,\n DashboardStatusValue,\n DashboardTriggerValue,\n InterfaceListInsights,\n OverviewMetric,\n} from \"./types\";\n\nexport type AutomationHealth =\n | \"healthy\"\n | \"failing\"\n | \"running\"\n | \"disabled\"\n | \"never-run\"\n | \"unknown\";\n\n/** Which caption of the manifest's `insights.health` block names each state. */\nexport const HEALTH_LABEL_KEYS: Record<\n AutomationHealth,\n keyof InterfaceListInsights[\"health\"]\n> = {\n healthy: \"healthy\",\n failing: \"failing\",\n running: \"running\",\n disabled: \"disabled\",\n \"never-run\": \"neverRun\",\n unknown: \"checking\",\n};\n\nexport interface AutomationRunSummary {\n /** Lifetime run count, from the response — not the sample's length. */\n total: number;\n /**\n * Lifetime COMPLETED-run count. From the response's `status_counts` when\n * the service reports it; an older service leaves that out, so the sample\n * stands in exactly when it holds the whole history (`total` ≤ its length)\n * and the count is null — unknowable, never guessed — otherwise.\n */\n completedTotal: number | null;\n latestRun: AutomationRun | null;\n /** Newest-first sample used by the list sparkline (same page as the summary). */\n recentRuns: AutomationRun[];\n /** COMPLETED over COMPLETED+FAILED in the sample. Null with no terminal runs. */\n recentSuccessRate: number | null;\n /** Mean completed_at − started_at over the sample's terminal runs. */\n averageDurationMs: number | null;\n}\n\n/** One automation's summary alongside its query state. */\nexport interface RunSummaryState {\n summary: AutomationRunSummary | null;\n isLoading: boolean;\n isError: boolean;\n}\n\ntype Summaries = ReadonlyMap<string, RunSummaryState>;\n\nconst TERMINAL_STATUSES = new Set<AutomationRunStatus>([\n AutomationRunStatus.COMPLETED,\n AutomationRunStatus.FAILED,\n]);\n\nfunction isTaskSuccessful(run: AutomationRun): boolean {\n const { badgeStatus } = getAutomationRunDisplay(run);\n return (\n badgeStatus === AutomationRunStatus.COMPLETED || badgeStatus === \"success\"\n );\n}\n\nfunction isTaskFailing(run: AutomationRun): boolean {\n const { badgeStatus } = getAutomationRunDisplay(run);\n return (\n badgeStatus === AutomationRunStatus.FAILED ||\n badgeStatus === \"failed\" ||\n badgeStatus === \"blocked\"\n );\n}\n\nexport function summarizeAutomationRuns(\n response: AutomationRunsResponse,\n): AutomationRunSummary {\n const terminal = response.runs.filter((run) =>\n TERMINAL_STATUSES.has(run.status),\n );\n const completed = terminal.filter(isTaskSuccessful).length;\n\n let completedTotal: number | null;\n if (response.status_counts) {\n completedTotal = response.status_counts[AutomationRunStatus.COMPLETED] ?? 0;\n } else if (response.total <= response.runs.length) {\n completedTotal = completed;\n } else {\n completedTotal = null;\n }\n\n const durations = terminal.flatMap((run) => {\n if (!run.completed_at) return [];\n const ms =\n new Date(run.completed_at).getTime() - new Date(run.started_at).getTime();\n return Number.isFinite(ms) && ms >= 0 ? [ms] : [];\n });\n\n return {\n total: response.total,\n completedTotal,\n latestRun: response.runs[0] ?? null,\n recentRuns: response.runs,\n recentSuccessRate:\n terminal.length === 0 ? null : completed / terminal.length,\n averageDurationMs:\n durations.length === 0\n ? null\n : durations.reduce((sum, ms) => sum + ms, 0) / durations.length,\n };\n}\n\n/**\n * A disabled automation is disabled no matter its history; while the summary\n * is unsettled nothing is claimed; after that the latest run speaks.\n */\nexport function deriveAutomationHealth(\n automation: Automation,\n state: RunSummaryState | undefined,\n): AutomationHealth {\n if (!automation.enabled) return \"disabled\";\n if (!state || state.isError || state.isLoading || !state.summary) {\n return \"unknown\";\n }\n const latest = state.summary.latestRun;\n if (!latest) return \"never-run\";\n if (isTaskFailing(latest)) return \"failing\";\n if (\n latest.status === AutomationRunStatus.PENDING ||\n latest.status === AutomationRunStatus.RUNNING\n ) {\n return \"running\";\n }\n return isTaskSuccessful(latest) ? \"healthy\" : \"unknown\";\n}\n\n/** \"—\" unknown, seconds under a minute, minutes under an hour, else \"1.5h\". */\nexport function formatCompactDuration(ms: number | null): string {\n if (ms === null || !Number.isFinite(ms)) return \"—\";\n if (ms < 60_000) return `${Math.max(1, Math.round(ms / 1000))}s`;\n if (ms < 3_600_000) return `${Math.max(1, Math.round(ms / 60_000))}m`;\n return `${(ms / 3_600_000).toFixed(1)}h`;\n}\n\n/** The list page's search predicate: name, prompt, repository, or model. */\nexport function matchesAutomationSearch(\n automation: Automation,\n query: string,\n): boolean {\n const q = query.toLowerCase();\n if (!q) return true;\n return (\n automation.name.toLowerCase().includes(q) ||\n (automation.prompt ?? \"\").toLowerCase().includes(q) ||\n automation.repository?.toLowerCase().includes(q) === true ||\n automation.model?.toLowerCase().includes(q) === true\n );\n}\n\nconst STATUS_PREDICATES: Record<\n Exclude<DashboardStatusValue, \"all\">,\n (automation: Automation, health: AutomationHealth) => boolean\n> = {\n active: (automation) => automation.enabled,\n failing: (_automation, health) => health === \"failing\",\n disabled: (automation) => !automation.enabled,\n};\n\nconst TRIGGER_PREDICATES: Record<\n Exclude<DashboardTriggerValue, \"all\">,\n (automation: Automation) => boolean\n> = {\n // The backend emits several scheduled-trigger aliases, so \"event\" is the\n // one exact kind and everything else is a schedule.\n event: (automation) => automation.trigger.type === \"event\",\n schedule: (automation) => automation.trigger.type !== \"event\",\n};\n\nfunction runCount(automation: Automation, byId: Summaries): number {\n return byId.get(automation.id)?.summary?.total ?? 0;\n}\n\nfunction lastRunTime(automation: Automation, byId: Summaries): number {\n const startedAt =\n byId.get(automation.id)?.summary?.latestRun?.started_at ??\n automation.last_triggered_at;\n if (!startedAt) return 0;\n const time = new Date(startedAt).getTime();\n return Number.isFinite(time) ? time : 0;\n}\n\nconst SORT_COMPARATORS: Record<\n DashboardSortValue,\n (a: Automation, b: Automation, byId: Summaries) => number\n> = {\n \"last-run\": (a, b, byId) => lastRunTime(b, byId) - lastRunTime(a, byId),\n runs: (a, b, byId) => runCount(b, byId) - runCount(a, byId),\n name: (a, b) => a.name.localeCompare(b.name),\n};\n\nexport interface DashboardViewState {\n search: string;\n status: DashboardStatusValue;\n trigger: DashboardTriggerValue;\n sort: DashboardSortValue;\n}\n\n/** The automations the dashboard shows, filtered and ordered. */\nexport function applyDashboardView(\n automations: readonly Automation[],\n view: DashboardViewState,\n byId: Summaries,\n): Automation[] {\n return automations\n .filter((automation) => {\n if (!matchesAutomationSearch(automation, view.search)) return false;\n if (\n view.status !== \"all\" &&\n !STATUS_PREDICATES[view.status](\n automation,\n deriveAutomationHealth(automation, byId.get(automation.id)),\n )\n ) {\n return false;\n }\n if (\n view.trigger !== \"all\" &&\n !TRIGGER_PREDICATES[view.trigger](automation)\n ) {\n return false;\n }\n return true;\n })\n .sort((a, b) => SORT_COMPARATORS[view.sort](a, b, byId));\n}\n\nexport interface OverviewTileValue {\n /** The formatted value the tile displays. */\n display: string;\n /** True when the metric's value is zero, which swaps in `zeroDetail`. */\n isZero: boolean;\n /** Values the tile's copy may substitute via `{{name}}`. */\n placeholderValues: Record<string, string | number>;\n}\n\nfunction loadedSummaries(\n automations: readonly Automation[],\n byId: Summaries,\n): AutomationRunSummary[] {\n return automations.flatMap((automation) => {\n const state = byId.get(automation.id);\n return state?.summary && !state.isLoading && !state.isError\n ? [state.summary]\n : [];\n });\n}\n\nconst TILE_COMPUTATIONS: Record<\n OverviewMetric,\n (automations: readonly Automation[], byId: Summaries) => OverviewTileValue\n> = {\n automations: (automations) => ({\n display: automations.length.toLocaleString(),\n isZero: automations.length === 0,\n placeholderValues: {\n active: automations.filter((automation) => automation.enabled).length,\n },\n }),\n \"needs-attention\": (automations, byId) => {\n const failing = automations.filter(\n (automation) =>\n deriveAutomationHealth(automation, byId.get(automation.id)) ===\n \"failing\",\n ).length;\n return {\n display: failing.toLocaleString(),\n isZero: failing === 0,\n placeholderValues: {},\n };\n },\n \"total-runs\": (automations, byId) => {\n const loaded = loadedSummaries(automations, byId);\n if (loaded.length === 0 && automations.length > 0) {\n return { display: \"—\", isZero: false, placeholderValues: {} };\n }\n const total = loaded.reduce((sum, summary) => sum + summary.total, 0);\n return {\n display: total.toLocaleString(),\n isZero: total === 0,\n placeholderValues: {},\n };\n },\n \"average-duration\": (automations, byId) => {\n const durations = loadedSummaries(automations, byId).flatMap((summary) =>\n summary.averageDurationMs === null ? [] : [summary.averageDurationMs],\n );\n const mean =\n durations.length === 0\n ? null\n : durations.reduce((sum, ms) => sum + ms, 0) / durations.length;\n return {\n display: formatCompactDuration(mean),\n isZero: false,\n placeholderValues: {},\n };\n },\n};\n\n/** The value behind one manifest-declared tile. */\nexport function computeOverviewTile(\n metric: OverviewMetric,\n automations: readonly Automation[],\n byId: Summaries,\n): OverviewTileValue {\n return TILE_COMPUTATIONS[metric](automations, byId);\n}\n"],"mappings":";AAgF0B,IAAI,IAAyB,CACrD,EAAoB,WACpB,EAAoB,MACtB,CAAC;AAiFD,SAAgB,EAAsB,GAA2B;CAI/D,OAHI,MAAO,QAAQ,CAAC,OAAO,SAAS,CAAE,IAAU,MAC5C,IAAK,MAAe,GAAG,KAAK,IAAI,GAAG,KAAK,MAAM,IAAK,GAAI,CAAC,EAAE,KAC1D,IAAK,OAAkB,GAAG,KAAK,IAAI,GAAG,KAAK,MAAM,IAAK,GAAM,CAAC,EAAE,KAC5D,IAAI,IAAK,MAAW,QAAQ,CAAC,EAAE;AACxC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require("../_virtual/_rolldown/runtime.cjs"),require("../i18n/index.cjs"),require("../utils/automation-catalog.cjs");const e=require("./automation-interface.cjs");function
|
|
1
|
+
require("../_virtual/_rolldown/runtime.cjs"),require("../i18n/index.cjs"),require("../utils/automation-catalog.cjs");const e=require("./automation-interface.cjs"),t=require("./manifest-local-validation.cjs");function n(t,n){let r=o(t,n);return r===`plugin`?e.getAutomationEndpoint(`createPlugin`):r===`upload`||r===`bundle`?i(`createBundle`):e.getAutomationEndpoint(`createPrompt`)}function r(){return i(`uploads`)}function i(t){let n=e.getAutomationEndpoint(t);if(!n)throw Error(`The published automation interface declares no '${t}' endpoint, so this deployment cannot create an automation from a script bundle.`);return n}function a(e){return e.setup.mode===`direct`&&e.setup.bundle!==void 0}function o(e,n){if(!e)return`prompt`;if(a(e))return`bundle`;let r=e.setup.actions??{};if(n&&n in r)return n;let i=t.actionKinds(e.setup);return i.length===1?i[0]:`prompt`}exports.automationCreateEndpoint=n,exports.automationUploadEndpoint=r;
|
|
2
2
|
//# sourceMappingURL=automation-setup.cjs.map
|
|
@@ -1 +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 i18n from \"#/i18n\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { findAutomationCommand } from \"#/utils/automation-catalog\";\nimport { getAutomationEndpoint } from \"./automation-interface\";\nimport {\n collectFields,\n fieldText,\n fieldValues,\n} from \"./manifest-local-validation\";\nimport { interpolateText, interpolateValue } from \"./manifest-template\";\nimport type {\n SetupBlock,\n SetupBundleConfigValue,\n SetupEntry,\n SetupFormValues,\n SetupRequestBody,\n SetupTriggerKind,\n} from \"./types\";\n\n/**\n * The creation endpoint a derived draft would be posted to. Resolved on call\n * rather than at import, because the endpoint is the interface manifest's and\n * this module loads whether or not one was admitted.\n *\n * A bundle entry is created through the raw endpoint, because what it sends is\n * a tarball it uploaded rather than arguments to a preset. Called without an\n * entry - as the import path does - it answers for a prompt.\n */\nexport function automationCreateEndpoint(entry?: SetupEntry): string {\n if (entry && isBundleEntry(entry)) {\n return requireBundleEndpoint(\"createBundle\");\n }\n return getAutomationEndpoint(\"createPrompt\");\n}\n\n/** Where a bundle's tarball is uploaded, before the create call. */\nexport function automationUploadEndpoint(): string {\n return requireBundleEndpoint(\"uploads\");\n}\n\n/** The endpoints a bundle entry cannot be created without. */\nconst BUNDLE_ENDPOINTS = [\"createBundle\", \"uploads\"] as const;\n\n/**\n * An endpoint only a bundle needs. The interface manifest may predate bundles,\n * and the host holds no path of its own to fall back to, so this is where that\n * runs out rather than somewhere deep in a request.\n */\nfunction requireBundleEndpoint(\n name: (typeof BUNDLE_ENDPOINTS)[number],\n): string {\n const path = getAutomationEndpoint(name);\n if (!path) {\n throw new Error(\n `The published automation interface declares no '${name}' endpoint, ` +\n \"so this deployment cannot create an automation from a script bundle.\",\n );\n }\n return path;\n}\n\n/**\n * The endpoints this entry needs that the published interface does not declare.\n *\n * Asked before the form renders rather than discovered at the moment of\n * creating: a pinned package that predates bundles can never answer, and the\n * dialog can say so while nothing has been filled in yet. Empty for every\n * entry that is not a bundle, which needs nothing beyond what the block has\n * always declared.\n */\nexport function missingCreateEndpoints(entry: SetupEntry): string[] {\n if (!isBundleEntry(entry)) return [];\n return BUNDLE_ENDPOINTS.filter((name) => !getAutomationEndpoint(name));\n}\n\n/** Whether this entry ships a script tarball instead of a prompt. */\nexport function isBundleEntry(entry: SetupEntry): boolean {\n return entry.setup.mode === \"direct\" && entry.setup.bundle !== undefined;\n}\n\n/**\n * The `tarball_path` a preflight draft carries.\n *\n * Preflight runs on every field blur and the upload happens once, at submit,\n * so there is no real path to send yet. The service checks this field's scheme\n * at preflight and its ownership only at creation, so a well-formed stand-in\n * validates exactly what preflight is for - the rest of the body - without\n * uploading an archive per keystroke.\n */\nexport const PREFLIGHT_TARBALL_PATH =\n \"oh-internal://uploads/00000000-0000-0000-0000-000000000000\";\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/** Every repository the form collected, whether the picker takes one or many. */\nfunction repositories(setup: SetupBlock, values: SetupFormValues): string[] {\n const picker = findRepoPickerField(setup);\n return picker ? fieldValues(values[picker.name]) : [];\n}\n\n/**\n * The created automation's name.\n *\n * One repository is worth naming; several are not, so the count stands in\n * rather than a list of names that would not fit.\n */\nfunction deriveName(entry: SetupEntry, values: SetupFormValues): string {\n const repos = repositories(entry.setup, values);\n if (repos.length === 0) return entry.name;\n if (repos.length === 1) return `${entry.name} - ${repos[0]}`;\n // The count is the one word here the host writes rather than reads off the\n // entry, so it is translated. There is no translator to pass in: the\n // derivation runs from a memo, an upload and a test alike.\n const count = i18n.t(I18nKey.SETUP$REPOSITORY_COUNT, {\n total: repos.length,\n });\n return `${entry.name} - ${count}`;\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 /** Bundle entries only: what the upload returned. */\n tarballPath: string = PREFLIGHT_TARBALL_PATH,\n): SetupRequestBody | null {\n const { setup } = entry;\n if (setup.mode !== \"direct\") return null;\n if (setup.bundle) return buildBundlePayload(entry, values, tarballPath);\n if (!setup.prompt) return null;\n\n const scope = { form: values, automation: entry };\n const repoPicker = findRepoPickerField(setup);\n const repos = repositories(setup, values);\n\n const payload: SetupRequestBody = {\n name: deriveName(entry, values),\n prompt: interpolateText(setup.prompt, scope),\n };\n\n if (repos.length > 0 && repoPicker?.field.provider) {\n const declared = REPO_PROPERTIES.filter((name) => name in values);\n payload.repos = repos.map((url) => ({\n url,\n ...Object.fromEntries(\n declared.map((name) => [name, fieldText(values[name])]),\n ),\n provider: repoPicker.field.provider as string,\n }));\n }\n\n // A filter is optional: an entry that declares none accepts every delivered\n // event, so the key is left off rather than sent empty.\n const trigger = buildTrigger(entry, values);\n if (trigger) payload.trigger = trigger;\n\n const template = buildTemplate(entry, values);\n if (template) payload.template = template;\n\n return payload;\n}\n\n/**\n * The `template` provenance a prompt entry sends, or undefined for one that\n * publishes no version.\n *\n * It is what lets the service recognise a second setup of the same entry as\n * the automation it already holds. The bundle path derives the same block from\n * the config the bundle declares; a prompt entry declares none, so the answer\n * to \"what was this created from\" is the form as submitted.\n */\nfunction buildTemplate(\n entry: SetupEntry,\n values: SetupFormValues,\n): SetupRequestBody | undefined {\n if (!entry.version) return undefined;\n return {\n id: entry.id,\n version: entry.version,\n config: { ...values },\n };\n}\n\n/**\n * The `trigger` object, read off the key and fields under `form.triggers`.\n *\n * Identical for both kinds of direct entry: only the create endpoint and what\n * the automation is told to do differ between a prompt and a bundle.\n */\nfunction buildTrigger(\n entry: SetupEntry,\n values: SetupFormValues,\n): SetupRequestBody | undefined {\n const trigger = getTrigger(entry.setup);\n if (!trigger) return undefined;\n\n const properties = TRIGGER_PROPERTIES[trigger.kind];\n const declared = Object.keys(trigger.fields).filter((name) =>\n properties.includes(name),\n );\n const repoPicker = findRepoPickerField(entry.setup);\n\n return {\n type: trigger.kind,\n ...Object.fromEntries(\n declared.map((name) => [name, fieldText(values[name])]),\n ),\n ...(trigger.kind === \"event\" && {\n source: repoPicker?.field.provider ?? \"\",\n ...(entry.setup.filter && {\n filter: interpolateText(entry.setup.filter, {\n form: values,\n automation: entry,\n }),\n }),\n }),\n };\n}\n\n/**\n * The raw create body a bundle entry produces.\n *\n * `tarball_path` is the one value neither declared nor derived: the host packs\n * and uploads the bundle first, and creates from what came back. `template` is\n * the provenance that makes enabling the same entry twice return the\n * automation that already exists rather than a second one.\n *\n * There is no `repos`: the raw endpoint has no such field, and a bundle's\n * script fetches what it needs itself.\n */\nfunction buildBundlePayload(\n entry: SetupEntry,\n values: SetupFormValues,\n tarballPath: string,\n): SetupRequestBody {\n const bundle = entry.setup.bundle!;\n const scope = { form: values, automation: entry };\n\n const payload: SetupRequestBody = {\n name: deriveName(entry, values),\n };\n\n const trigger = buildTrigger(entry, values);\n if (trigger) payload.trigger = trigger;\n\n payload.tarball_path = tarballPath;\n payload.entrypoint = bundle.entrypoint;\n if (bundle.setupScript) payload.setup_script_path = bundle.setupScript;\n if (bundle.timeout !== undefined) payload.timeout = bundle.timeout;\n payload.template = {\n id: entry.id,\n version: bundle.version,\n config: interpolateConfig(bundle.config, scope) as SetupRequestBody,\n };\n\n return payload;\n}\n\n/**\n * Placeholder substitution over the config tree. Only string leaves are\n * templated; a number, a boolean or a null is written through as itself, so an\n * entry can state a value the script reads as the type it expects.\n */\nfunction interpolateConfig(\n node: SetupBundleConfigValue,\n scope: Parameters<typeof interpolateText>[1],\n): SetupBundleConfigValue {\n if (typeof node === \"string\") {\n return interpolateValue(node, scope);\n }\n if (Array.isArray(node)) {\n return node.map((item) => interpolateConfig(item, scope));\n }\n if (typeof node === \"object\" && node !== null) {\n return Object.fromEntries(\n Object.entries(node).map(([key, value]) => [\n key,\n interpolateConfig(value, scope),\n ]),\n );\n }\n return node;\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: automationCreateEndpoint(entry),\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":"mKA2CA,SAAgB,EAAyB,EAA4B,CAInE,OAHI,GAAS,EAAc,CAAK,EACvB,EAAsB,cAAc,EAEtC,EAAA,sBAAsB,cAAc,CAC7C,CAGA,SAAgB,GAAmC,CACjD,OAAO,EAAsB,SAAS,CACxC,CAUA,SAAS,EACP,EACQ,CACR,IAAM,EAAO,EAAA,sBAAsB,CAAI,EACvC,GAAI,CAAC,EACH,MAAU,MACR,mDAAmD,EAAK,iFAE1D,EAEF,OAAO,CACT,CAiBA,SAAgB,EAAc,EAA4B,CACxD,OAAO,EAAM,MAAM,OAAS,UAAY,EAAM,MAAM,SAAW,IAAA,EACjE"}
|
|
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 i18n from \"#/i18n\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { findAutomationCommand } from \"#/utils/automation-catalog\";\nimport { getAutomationEndpoint } from \"./automation-interface\";\nimport {\n actionKinds,\n collectFields,\n fieldText,\n fieldValues,\n} from \"./manifest-local-validation\";\nimport { interpolateText, interpolateValue } from \"./manifest-template\";\nimport type {\n SetupActionKind,\n SetupBlock,\n SetupBundleConfigValue,\n SetupEntry,\n SetupFormValues,\n SetupRequestBody,\n SetupTriggerKind,\n} from \"./types\";\n\n/**\n * The creation endpoint a derived draft would be posted to. Resolved on call\n * rather than at import, because the endpoint is the interface manifest's and\n * this module loads whether or not one was admitted.\n *\n * A bundle entry is created through the raw endpoint, because what it sends is\n * a tarball it uploaded rather than arguments to a preset. Called without an\n * entry - as the import path does - it answers for a prompt.\n */\nexport function automationCreateEndpoint(\n entry?: SetupEntry,\n selectedAction?: string | null,\n): string {\n const kind = selectedActionKind(entry, selectedAction);\n if (kind === \"plugin\") return getAutomationEndpoint(\"createPlugin\");\n if (kind === \"upload\" || kind === \"bundle\") {\n return requireBundleEndpoint(\"createBundle\");\n }\n return getAutomationEndpoint(\"createPrompt\");\n}\n\n/** Where a bundle's tarball is uploaded, before the create call. */\nexport function automationUploadEndpoint(): string {\n return requireBundleEndpoint(\"uploads\");\n}\n\n/** The endpoints a bundle entry cannot be created without. */\nconst BUNDLE_ENDPOINTS = [\"createBundle\", \"uploads\"] as const;\n\n/**\n * An endpoint only a bundle needs. The interface manifest may predate bundles,\n * and the host holds no path of its own to fall back to, so this is where that\n * runs out rather than somewhere deep in a request.\n */\nfunction requireBundleEndpoint(\n name: (typeof BUNDLE_ENDPOINTS)[number],\n): string {\n const path = getAutomationEndpoint(name);\n if (!path) {\n throw new Error(\n `The published automation interface declares no '${name}' endpoint, ` +\n \"so this deployment cannot create an automation from a script bundle.\",\n );\n }\n return path;\n}\n\n/**\n * The endpoints this entry needs that the published interface does not declare.\n *\n * Asked before the form renders rather than discovered at the moment of\n * creating: a pinned package that predates bundles can never answer, and the\n * dialog can say so while nothing has been filled in yet. Empty for every\n * entry that is not a bundle, which needs nothing beyond what the block has\n * always declared.\n */\nfunction missingEndpointsForAction(kind: SetupActionKind | \"bundle\"): string[] {\n return kind === \"upload\" || kind === \"bundle\"\n ? BUNDLE_ENDPOINTS.filter((name) => !getAutomationEndpoint(name))\n : [];\n}\n\nexport function missingCreateEndpoints(\n entry: SetupEntry,\n selectedAction?: string | null,\n): string[] {\n if (isBundleEntry(entry)) return missingEndpointsForAction(\"bundle\");\n if (!entry.setup.actions) return [];\n\n if (selectedAction && selectedAction in entry.setup.actions) {\n return missingEndpointsForAction(selectedAction as SetupActionKind);\n }\n\n const missingByAction = actionKinds(entry.setup).map(\n missingEndpointsForAction,\n );\n if (missingByAction.some((missing) => missing.length === 0)) return [];\n return Array.from(new Set(missingByAction.flat()));\n}\n\n/** Whether this entry ships a script tarball instead of a prompt. */\nexport function isBundleEntry(entry: SetupEntry): boolean {\n return entry.setup.mode === \"direct\" && entry.setup.bundle !== undefined;\n}\n\nexport function isUploadAction(\n entry: SetupEntry,\n selectedAction?: string | null,\n): boolean {\n return selectedActionKind(entry, selectedAction) === \"upload\";\n}\n\nexport function selectedActionKind(\n entry: SetupEntry | undefined,\n selectedAction?: string | null,\n): SetupActionKind | \"bundle\" | \"prompt\" {\n if (!entry) return \"prompt\";\n if (isBundleEntry(entry)) return \"bundle\";\n const actions = entry.setup.actions ?? {};\n if (selectedAction && selectedAction in actions) {\n return selectedAction as SetupActionKind;\n }\n const keys = actionKinds(entry.setup);\n if (keys.length === 1) return keys[0];\n return \"prompt\";\n}\n\n/**\n * The `tarball_path` a preflight draft carries.\n *\n * Preflight runs on every field blur and the upload happens once, at submit,\n * so there is no real path to send yet. The service checks this field's scheme\n * at preflight and its ownership only at creation, so a well-formed stand-in\n * validates exactly what preflight is for - the rest of the body - without\n * uploading an archive per keystroke.\n */\nexport const PREFLIGHT_TARBALL_PATH =\n \"oh-internal://uploads/00000000-0000-0000-0000-000000000000\";\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: [\"source\", \"on\"],\n};\n\nconst OPTIONAL_CREATE_PROPERTIES = [\"model\", \"timeout\"] as const;\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(\n setup: SetupBlock,\n selectedAction?: string | null,\n) {\n const match = Object.entries(collectFields(setup, null, selectedAction)).find(\n ([, field]) => field.type === \"repo-picker\",\n );\n return match ? { name: match[0], field: match[1] } : null;\n}\n\n/** Every repository the form collected, whether the picker takes one or many. */\nfunction repositories(\n setup: SetupBlock,\n values: SetupFormValues,\n selectedAction?: string | null,\n): string[] {\n const picker = findRepoPickerField(setup, selectedAction);\n return picker ? fieldValues(values[picker.name]) : [];\n}\n\n/**\n * The created automation's name.\n *\n * One repository is worth naming; several are not, so the count stands in\n * rather than a list of names that would not fit.\n */\nfunction deriveName(\n entry: SetupEntry,\n values: SetupFormValues,\n selectedAction?: string | null,\n): string {\n if (\n \"name\" in collectFields(entry.setup, null, selectedAction) &&\n \"name\" in values\n ) {\n return fieldText(values.name);\n }\n\n const repos = repositories(entry.setup, values, selectedAction);\n if (repos.length === 0) return entry.name;\n if (repos.length === 1) return `${entry.name} - ${repos[0]}`;\n // The count is the one word here the host writes rather than reads off the\n // entry, so it is translated. There is no translator to pass in: the\n // derivation runs from a memo, an upload and a test alike.\n const count = i18n.t(I18nKey.SETUP$REPOSITORY_COUNT, {\n total: repos.length,\n });\n return `${entry.name} - ${count}`;\n}\n\n/** The active trigger kind for a direct entry, with its fields. */\nfunction getTrigger(setup: SetupBlock, selectedTrigger?: string | null) {\n const triggers = setup.form.triggers ?? {};\n const entries = Object.entries(triggers);\n if (entries.length === 0) return null;\n const match =\n selectedTrigger && selectedTrigger in triggers\n ? [selectedTrigger, triggers[selectedTrigger as SetupTriggerKind]]\n : entries.length === 1\n ? entries[0]\n : null;\n if (!match) return null;\n const [kind, fields] = match;\n return { kind: kind as SetupTriggerKind, fields: fields ?? {} };\n}\n\nfunction hasPayloadValue(\n value: SetupRequestBody[string] | undefined,\n): value is SetupRequestBody[string] {\n return value !== undefined && value !== null && value !== \"\";\n}\n\nfunction fieldPayloadValue(\n fieldType: string | undefined,\n value: SetupFormValues[string],\n) {\n if (fieldType === \"number\") {\n const text = fieldText(value);\n return text === \"\" ? undefined : Number(text);\n }\n return fieldText(value);\n}\n\nfunction optionalCreateProperties(\n setup: SetupBlock,\n values: SetupFormValues,\n selectedAction?: string | null,\n): SetupRequestBody {\n return Object.fromEntries(\n OPTIONAL_CREATE_PROPERTIES.flatMap((name) => {\n const field = collectFields(setup, null, selectedAction)[name];\n const value = fieldPayloadValue(field?.type, values[name]);\n return hasPayloadValue(value) ? [[name, value]] : [];\n }),\n ) as SetupRequestBody;\n}\n\nfunction repositoryProperty(\n setup: SetupBlock,\n values: SetupFormValues,\n selectedAction?: string | null,\n): SetupRequestBody {\n const repoPicker = findRepoPickerField(setup, selectedAction);\n const repos = repositories(setup, values, selectedAction);\n if (repos.length === 0 || !repoPicker?.field.provider) return {};\n\n const declared = REPO_PROPERTIES.filter((name) =>\n hasPayloadValue(fieldText(values[name])),\n );\n return {\n repos: repos.map((url) => ({\n url,\n ...Object.fromEntries(\n declared.map((name) => [name, fieldText(values[name])]),\n ),\n provider: repoPicker.field.provider as string,\n })),\n };\n}\n\nfunction parsePluginSources(\n value: SetupRequestBody[string],\n): SetupRequestBody[string] {\n if (!hasPayloadValue(value) || typeof value !== \"string\") return value;\n const trimmed = value.trim();\n if (!trimmed) return [];\n if (trimmed.startsWith(\"[\")) {\n try {\n return JSON.parse(trimmed) as SetupRequestBody[string];\n } catch {\n return [{ source: trimmed }];\n }\n }\n return trimmed\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter(Boolean)\n .map((line) => {\n const [source, ...parts] = line.split(/\\s+/);\n const plugin: Record<string, string> = { source };\n parts.forEach((part) => {\n const [key, ...raw] = part.split(\"=\");\n const valuePart = raw.join(\"=\");\n if ((key === \"ref\" || key === \"repo_path\") && valuePart) {\n plugin[key] = valuePart;\n }\n });\n return plugin;\n });\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 /** Bundle/upload entries only: what the upload returned. */\n tarballPath: string = PREFLIGHT_TARBALL_PATH,\n selectedTrigger?: string | null,\n selectedActionKey?: string | null,\n): SetupRequestBody | null {\n const { setup } = entry;\n if (setup.mode !== \"direct\") return null;\n if (setup.bundle) {\n return buildBundlePayload(entry, values, tarballPath, selectedTrigger);\n }\n if (setup.actions) {\n return buildActionPayload(\n entry,\n values,\n tarballPath,\n selectedTrigger,\n selectedActionKey,\n );\n }\n if (!setup.prompt) return null;\n\n const scope = { form: values, automation: entry };\n\n const payload: SetupRequestBody = {\n name: deriveName(entry, values),\n prompt: interpolateText(setup.prompt, scope),\n ...optionalCreateProperties(setup, values),\n ...repositoryProperty(setup, values),\n };\n\n const trigger = buildTrigger(entry, values, selectedTrigger);\n if (trigger) payload.trigger = trigger;\n\n const template = buildTemplate(entry, values);\n if (template) payload.template = template;\n\n return payload;\n}\n\nfunction buildActionPayload(\n entry: SetupEntry,\n values: SetupFormValues,\n tarballPath: string,\n selectedTrigger?: string | null,\n selectedActionKey?: string | null,\n): SetupRequestBody | null {\n const kind = selectedActionKind(entry, selectedActionKey);\n if (kind === \"bundle\") return null;\n\n const setup = entry.setup;\n const scope = { form: values, automation: entry };\n const payload: SetupRequestBody = {\n name: deriveName(entry, values, selectedActionKey),\n };\n\n const trigger = buildTrigger(\n entry,\n values,\n selectedTrigger,\n selectedActionKey,\n );\n if (trigger) payload.trigger = trigger;\n\n Object.assign(\n payload,\n optionalCreateProperties(setup, values, selectedActionKey),\n );\n\n if (kind === \"upload\") {\n const action = setup.actions?.upload;\n if (!action) return null;\n payload.tarball_path =\n tarballPath || interpolateText(action.tarballPath, scope);\n payload.entrypoint = interpolateText(action.entrypoint, scope);\n if (action.setupScript) {\n const setupScript = interpolateText(action.setupScript, scope);\n if (hasPayloadValue(setupScript)) payload.setup_script_path = setupScript;\n }\n return payload;\n }\n\n const action =\n kind === \"plugin\" ? setup.actions?.plugin : setup.actions?.prompt;\n if (!action) return null;\n payload.prompt = interpolateText(action.prompt, scope);\n Object.assign(payload, repositoryProperty(setup, values, selectedActionKey));\n\n if (kind === \"plugin\") {\n const pluginAction = setup.actions?.plugin;\n if (!pluginAction) return null;\n payload.plugins = parsePluginSources(\n interpolateValue(pluginAction.plugins, scope),\n );\n }\n\n const template = buildTemplate(entry, values);\n if (template) payload.template = template;\n\n return payload;\n}\n\n/**\n * The `template` provenance a prompt entry sends, or undefined for one that\n * publishes no version.\n *\n * It is what lets the service recognise a second setup of the same entry as\n * the automation it already holds. The bundle path derives the same block from\n * the config the bundle declares; a prompt entry declares none, so the answer\n * to \"what was this created from\" is the form as submitted.\n */\nfunction buildTemplate(\n entry: SetupEntry,\n values: SetupFormValues,\n): SetupRequestBody | undefined {\n if (!entry.version) return undefined;\n return {\n id: entry.id,\n version: entry.version,\n config: { ...values } as SetupRequestBody,\n };\n}\n\n/**\n * The `trigger` object, read off the key and fields under `form.triggers`.\n *\n * Identical for both kinds of direct entry: only the create endpoint and what\n * the automation is told to do differ between a prompt and a bundle.\n */\nfunction buildTrigger(\n entry: SetupEntry,\n values: SetupFormValues,\n selectedTrigger?: string | null,\n selectedAction?: string | null,\n): SetupRequestBody | undefined {\n const trigger = getTrigger(entry.setup, selectedTrigger);\n if (!trigger) return undefined;\n\n const properties = TRIGGER_PROPERTIES[trigger.kind];\n const declared = Object.keys(trigger.fields).filter((name) =>\n properties.includes(name),\n );\n const repoPicker = findRepoPickerField(entry.setup, selectedAction);\n const derived = Object.fromEntries(\n declared\n .map((name) => [name, fieldText(values[name])])\n .filter(([, value]) => hasPayloadValue(value)),\n );\n\n const filter = entry.setup.filter\n ? interpolateText(entry.setup.filter, {\n form: values,\n automation: entry,\n })\n : undefined;\n\n return {\n type: trigger.kind,\n ...derived,\n ...(trigger.kind === \"event\" && {\n ...(!(\"source\" in derived) && {\n source: repoPicker?.field.provider ?? \"\",\n }),\n ...(hasPayloadValue(filter) && { filter }),\n }),\n };\n}\n\n/**\n * The raw create body a bundle entry produces.\n *\n * `tarball_path` is the one value neither declared nor derived: the host packs\n * and uploads the bundle first, and creates from what came back. `template` is\n * the provenance that makes enabling the same entry twice return the\n * automation that already exists rather than a second one.\n *\n * There is no `repos`: the raw endpoint has no such field, and a bundle's\n * script fetches what it needs itself.\n */\nfunction buildBundlePayload(\n entry: SetupEntry,\n values: SetupFormValues,\n tarballPath: string,\n selectedTrigger?: string | null,\n): SetupRequestBody {\n const bundle = entry.setup.bundle!;\n const scope = { form: values, automation: entry };\n\n const payload: SetupRequestBody = {\n name: deriveName(entry, values),\n };\n\n const trigger = buildTrigger(entry, values, selectedTrigger);\n if (trigger) payload.trigger = trigger;\n\n payload.tarball_path = tarballPath;\n payload.entrypoint = bundle.entrypoint;\n if (bundle.setupScript) payload.setup_script_path = bundle.setupScript;\n if (bundle.timeout !== undefined) payload.timeout = bundle.timeout;\n payload.template = {\n id: entry.id,\n version: bundle.version,\n config: interpolateConfig(bundle.config, scope) as SetupRequestBody,\n };\n\n return payload;\n}\n\n/**\n * Placeholder substitution over the config tree. Only string leaves are\n * templated; a number, a boolean or a null is written through as itself, so an\n * entry can state a value the script reads as the type it expects.\n */\nfunction interpolateConfig(\n node: SetupBundleConfigValue,\n scope: Parameters<typeof interpolateText>[1],\n): SetupBundleConfigValue {\n if (typeof node === \"string\") {\n return interpolateValue(node, scope);\n }\n if (Array.isArray(node)) {\n return node.map((item) => interpolateConfig(item, scope));\n }\n if (typeof node === \"object\" && node !== null) {\n return Object.fromEntries(\n Object.entries(node).map(([key, value]) => [\n key,\n interpolateConfig(value, scope),\n ]),\n );\n }\n return node;\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 selectedTrigger?: string | null,\n selectedAction?: string | null,\n): SetupRequestBody | null {\n const draft = buildCreatePayload(\n entry,\n values,\n PREFLIGHT_TARBALL_PATH,\n selectedTrigger,\n selectedAction,\n );\n if (!draft) return null;\n\n return {\n automationId: entry.id,\n endpoint: automationCreateEndpoint(entry, selectedAction),\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(\n entry: SetupEntry,\n selectedTrigger?: string | null,\n selectedAction?: string | null,\n): Record<string, string[]> {\n const mapping: Record<string, string[]> = {};\n\n const triggerEntries = Object.keys(entry.setup.form.triggers ?? {});\n const triggerVariants = selectedTrigger\n ? [selectedTrigger]\n : triggerEntries.length > 1\n ? triggerEntries\n : [null];\n\n const actionEntries = actionKinds(entry.setup);\n const actionVariants = selectedAction\n ? [selectedAction]\n : actionEntries.length > 1\n ? actionEntries\n : [null];\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) {\n mapping[path] = Array.from(\n new Set([...(mapping[path] ?? []), ...names]),\n );\n }\n }\n };\n\n actionVariants.forEach((actionVariant) => {\n triggerVariants.forEach((triggerVariant) => {\n const names = Object.keys(\n collectFields(entry.setup, triggerVariant, actionVariant),\n );\n const variantStandIns = Object.fromEntries(\n names.map((name) => [name, `{{form.${name}}}`]),\n );\n const template = buildCreatePayload(\n entry,\n variantStandIns,\n actionVariant === \"upload\" ? \"\" : PREFLIGHT_TARBALL_PATH,\n triggerVariant,\n actionVariant,\n );\n if (template) walk(template, \"\");\n });\n });\n return mapping;\n}\n"],"mappings":"gNA6CA,SAAgB,EACd,EACA,EACQ,CACR,IAAM,EAAO,EAAmB,EAAO,CAAc,EAKrD,OAJI,IAAS,SAAiB,EAAA,sBAAsB,cAAc,EAC9D,IAAS,UAAY,IAAS,SACzB,EAAsB,cAAc,EAEtC,EAAA,sBAAsB,cAAc,CAC7C,CAGA,SAAgB,GAAmC,CACjD,OAAO,EAAsB,SAAS,CACxC,CAUA,SAAS,EACP,EACQ,CACR,IAAM,EAAO,EAAA,sBAAsB,CAAI,EACvC,GAAI,CAAC,EACH,MAAU,MACR,mDAAmD,EAAK,iFAE1D,EAEF,OAAO,CACT,CAoCA,SAAgB,EAAc,EAA4B,CACxD,OAAO,EAAM,MAAM,OAAS,UAAY,EAAM,MAAM,SAAW,IAAA,EACjE,CASA,SAAgB,EACd,EACA,EACuC,CACvC,GAAI,CAAC,EAAO,MAAO,SACnB,GAAI,EAAc,CAAK,EAAG,MAAO,SACjC,IAAM,EAAU,EAAM,MAAM,SAAW,CAAC,EACxC,GAAI,GAAkB,KAAkB,EACtC,OAAO,EAET,IAAM,EAAO,EAAA,YAAY,EAAM,KAAK,EAEpC,OADI,EAAK,SAAW,EAAU,EAAK,GAC5B,QACT"}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* were verified against the live service. The create model is `extra="forbid"`,
|
|
13
13
|
* so any divergence is a hard 422 rather than a dropped field.
|
|
14
14
|
*/
|
|
15
|
-
import type { SetupEntry, SetupFormValues, SetupRequestBody } from "./types";
|
|
15
|
+
import type { SetupActionKind, SetupEntry, SetupFormValues, SetupRequestBody } from "./types";
|
|
16
16
|
/**
|
|
17
17
|
* The creation endpoint a derived draft would be posted to. Resolved on call
|
|
18
18
|
* rather than at import, because the endpoint is the interface manifest's and
|
|
@@ -22,21 +22,14 @@ import type { SetupEntry, SetupFormValues, SetupRequestBody } from "./types";
|
|
|
22
22
|
* a tarball it uploaded rather than arguments to a preset. Called without an
|
|
23
23
|
* entry - as the import path does - it answers for a prompt.
|
|
24
24
|
*/
|
|
25
|
-
export declare function automationCreateEndpoint(entry?: SetupEntry): string;
|
|
25
|
+
export declare function automationCreateEndpoint(entry?: SetupEntry, selectedAction?: string | null): string;
|
|
26
26
|
/** Where a bundle's tarball is uploaded, before the create call. */
|
|
27
27
|
export declare function automationUploadEndpoint(): string;
|
|
28
|
-
|
|
29
|
-
* The endpoints this entry needs that the published interface does not declare.
|
|
30
|
-
*
|
|
31
|
-
* Asked before the form renders rather than discovered at the moment of
|
|
32
|
-
* creating: a pinned package that predates bundles can never answer, and the
|
|
33
|
-
* dialog can say so while nothing has been filled in yet. Empty for every
|
|
34
|
-
* entry that is not a bundle, which needs nothing beyond what the block has
|
|
35
|
-
* always declared.
|
|
36
|
-
*/
|
|
37
|
-
export declare function missingCreateEndpoints(entry: SetupEntry): string[];
|
|
28
|
+
export declare function missingCreateEndpoints(entry: SetupEntry, selectedAction?: string | null): string[];
|
|
38
29
|
/** Whether this entry ships a script tarball instead of a prompt. */
|
|
39
30
|
export declare function isBundleEntry(entry: SetupEntry): boolean;
|
|
31
|
+
export declare function isUploadAction(entry: SetupEntry, selectedAction?: string | null): boolean;
|
|
32
|
+
export declare function selectedActionKind(entry: SetupEntry | undefined, selectedAction?: string | null): SetupActionKind | "bundle" | "prompt";
|
|
40
33
|
/**
|
|
41
34
|
* The `tarball_path` a preflight draft carries.
|
|
42
35
|
*
|
|
@@ -59,13 +52,13 @@ export declare const PREFLIGHT_TARBALL_PATH = "oh-internal://uploads/00000000-00
|
|
|
59
52
|
* instead of sending a body.
|
|
60
53
|
*/
|
|
61
54
|
export declare function buildCreatePayload(entry: SetupEntry, values: SetupFormValues,
|
|
62
|
-
/** Bundle entries only: what the upload returned. */
|
|
63
|
-
tarballPath?: string): SetupRequestBody | null;
|
|
55
|
+
/** Bundle/upload entries only: what the upload returned. */
|
|
56
|
+
tarballPath?: string, selectedTrigger?: string | null, selectedActionKey?: string | null): SetupRequestBody | null;
|
|
64
57
|
/**
|
|
65
58
|
* The preflight body the host sends. The same shape for every entry, so no
|
|
66
59
|
* entry declares it.
|
|
67
60
|
*/
|
|
68
|
-
export declare function buildPreflightBody(entry: SetupEntry, values: SetupFormValues): SetupRequestBody | null;
|
|
61
|
+
export declare function buildPreflightBody(entry: SetupEntry, values: SetupFormValues, selectedTrigger?: string | null, selectedAction?: string | null): SetupRequestBody | null;
|
|
69
62
|
/**
|
|
70
63
|
* What an assisted entry sends into the conversation that finishes setup.
|
|
71
64
|
*
|
|
@@ -83,4 +76,4 @@ export declare function buildAssistedMessage(entry: SetupEntry, values: SetupFor
|
|
|
83
76
|
* each field standing in for its own value recovers the mapping exactly, so an
|
|
84
77
|
* entry does not declare it.
|
|
85
78
|
*/
|
|
86
|
-
export declare function deriveErrorMap(entry: SetupEntry): Record<string, string[]>;
|
|
79
|
+
export declare function deriveErrorMap(entry: SetupEntry, selectedTrigger?: string | null, selectedAction?: string | null): Record<string, string[]>;
|
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
import "../i18n/index.js";
|
|
2
2
|
import "../utils/automation-catalog.js";
|
|
3
3
|
import { getAutomationEndpoint as e } from "./automation-interface.js";
|
|
4
|
+
import { actionKinds as t } from "./manifest-local-validation.js";
|
|
4
5
|
//#region src/manifests/automation-setup.ts
|
|
5
|
-
function
|
|
6
|
-
|
|
6
|
+
function n(t, n) {
|
|
7
|
+
let r = o(t, n);
|
|
8
|
+
return r === "plugin" ? e("createPlugin") : r === "upload" || r === "bundle" ? i("createBundle") : e("createPrompt");
|
|
7
9
|
}
|
|
8
|
-
function
|
|
9
|
-
return
|
|
10
|
+
function r() {
|
|
11
|
+
return i("uploads");
|
|
10
12
|
}
|
|
11
|
-
function
|
|
13
|
+
function i(t) {
|
|
12
14
|
let n = e(t);
|
|
13
15
|
if (!n) throw Error(`The published automation interface declares no '${t}' endpoint, so this deployment cannot create an automation from a script bundle.`);
|
|
14
16
|
return n;
|
|
15
17
|
}
|
|
16
|
-
function
|
|
18
|
+
function a(e) {
|
|
17
19
|
return e.setup.mode === "direct" && e.setup.bundle !== void 0;
|
|
18
20
|
}
|
|
21
|
+
function o(e, n) {
|
|
22
|
+
if (!e) return "prompt";
|
|
23
|
+
if (a(e)) return "bundle";
|
|
24
|
+
let r = e.setup.actions ?? {};
|
|
25
|
+
if (n && n in r) return n;
|
|
26
|
+
let i = t(e.setup);
|
|
27
|
+
return i.length === 1 ? i[0] : "prompt";
|
|
28
|
+
}
|
|
19
29
|
//#endregion
|
|
20
|
-
export {
|
|
30
|
+
export { n as automationCreateEndpoint, r as automationUploadEndpoint };
|
|
21
31
|
|
|
22
32
|
//# sourceMappingURL=automation-setup.js.map
|
|
@@ -1 +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 i18n from \"#/i18n\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { findAutomationCommand } from \"#/utils/automation-catalog\";\nimport { getAutomationEndpoint } from \"./automation-interface\";\nimport {\n collectFields,\n fieldText,\n fieldValues,\n} from \"./manifest-local-validation\";\nimport { interpolateText, interpolateValue } from \"./manifest-template\";\nimport type {\n SetupBlock,\n SetupBundleConfigValue,\n SetupEntry,\n SetupFormValues,\n SetupRequestBody,\n SetupTriggerKind,\n} from \"./types\";\n\n/**\n * The creation endpoint a derived draft would be posted to. Resolved on call\n * rather than at import, because the endpoint is the interface manifest's and\n * this module loads whether or not one was admitted.\n *\n * A bundle entry is created through the raw endpoint, because what it sends is\n * a tarball it uploaded rather than arguments to a preset. Called without an\n * entry - as the import path does - it answers for a prompt.\n */\nexport function automationCreateEndpoint(entry?: SetupEntry): string {\n if (entry && isBundleEntry(entry)) {\n return requireBundleEndpoint(\"createBundle\");\n }\n return getAutomationEndpoint(\"createPrompt\");\n}\n\n/** Where a bundle's tarball is uploaded, before the create call. */\nexport function automationUploadEndpoint(): string {\n return requireBundleEndpoint(\"uploads\");\n}\n\n/** The endpoints a bundle entry cannot be created without. */\nconst BUNDLE_ENDPOINTS = [\"createBundle\", \"uploads\"] as const;\n\n/**\n * An endpoint only a bundle needs. The interface manifest may predate bundles,\n * and the host holds no path of its own to fall back to, so this is where that\n * runs out rather than somewhere deep in a request.\n */\nfunction requireBundleEndpoint(\n name: (typeof BUNDLE_ENDPOINTS)[number],\n): string {\n const path = getAutomationEndpoint(name);\n if (!path) {\n throw new Error(\n `The published automation interface declares no '${name}' endpoint, ` +\n \"so this deployment cannot create an automation from a script bundle.\",\n );\n }\n return path;\n}\n\n/**\n * The endpoints this entry needs that the published interface does not declare.\n *\n * Asked before the form renders rather than discovered at the moment of\n * creating: a pinned package that predates bundles can never answer, and the\n * dialog can say so while nothing has been filled in yet. Empty for every\n * entry that is not a bundle, which needs nothing beyond what the block has\n * always declared.\n */\nexport function missingCreateEndpoints(entry: SetupEntry): string[] {\n if (!isBundleEntry(entry)) return [];\n return BUNDLE_ENDPOINTS.filter((name) => !getAutomationEndpoint(name));\n}\n\n/** Whether this entry ships a script tarball instead of a prompt. */\nexport function isBundleEntry(entry: SetupEntry): boolean {\n return entry.setup.mode === \"direct\" && entry.setup.bundle !== undefined;\n}\n\n/**\n * The `tarball_path` a preflight draft carries.\n *\n * Preflight runs on every field blur and the upload happens once, at submit,\n * so there is no real path to send yet. The service checks this field's scheme\n * at preflight and its ownership only at creation, so a well-formed stand-in\n * validates exactly what preflight is for - the rest of the body - without\n * uploading an archive per keystroke.\n */\nexport const PREFLIGHT_TARBALL_PATH =\n \"oh-internal://uploads/00000000-0000-0000-0000-000000000000\";\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/** Every repository the form collected, whether the picker takes one or many. */\nfunction repositories(setup: SetupBlock, values: SetupFormValues): string[] {\n const picker = findRepoPickerField(setup);\n return picker ? fieldValues(values[picker.name]) : [];\n}\n\n/**\n * The created automation's name.\n *\n * One repository is worth naming; several are not, so the count stands in\n * rather than a list of names that would not fit.\n */\nfunction deriveName(entry: SetupEntry, values: SetupFormValues): string {\n const repos = repositories(entry.setup, values);\n if (repos.length === 0) return entry.name;\n if (repos.length === 1) return `${entry.name} - ${repos[0]}`;\n // The count is the one word here the host writes rather than reads off the\n // entry, so it is translated. There is no translator to pass in: the\n // derivation runs from a memo, an upload and a test alike.\n const count = i18n.t(I18nKey.SETUP$REPOSITORY_COUNT, {\n total: repos.length,\n });\n return `${entry.name} - ${count}`;\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 /** Bundle entries only: what the upload returned. */\n tarballPath: string = PREFLIGHT_TARBALL_PATH,\n): SetupRequestBody | null {\n const { setup } = entry;\n if (setup.mode !== \"direct\") return null;\n if (setup.bundle) return buildBundlePayload(entry, values, tarballPath);\n if (!setup.prompt) return null;\n\n const scope = { form: values, automation: entry };\n const repoPicker = findRepoPickerField(setup);\n const repos = repositories(setup, values);\n\n const payload: SetupRequestBody = {\n name: deriveName(entry, values),\n prompt: interpolateText(setup.prompt, scope),\n };\n\n if (repos.length > 0 && repoPicker?.field.provider) {\n const declared = REPO_PROPERTIES.filter((name) => name in values);\n payload.repos = repos.map((url) => ({\n url,\n ...Object.fromEntries(\n declared.map((name) => [name, fieldText(values[name])]),\n ),\n provider: repoPicker.field.provider as string,\n }));\n }\n\n // A filter is optional: an entry that declares none accepts every delivered\n // event, so the key is left off rather than sent empty.\n const trigger = buildTrigger(entry, values);\n if (trigger) payload.trigger = trigger;\n\n const template = buildTemplate(entry, values);\n if (template) payload.template = template;\n\n return payload;\n}\n\n/**\n * The `template` provenance a prompt entry sends, or undefined for one that\n * publishes no version.\n *\n * It is what lets the service recognise a second setup of the same entry as\n * the automation it already holds. The bundle path derives the same block from\n * the config the bundle declares; a prompt entry declares none, so the answer\n * to \"what was this created from\" is the form as submitted.\n */\nfunction buildTemplate(\n entry: SetupEntry,\n values: SetupFormValues,\n): SetupRequestBody | undefined {\n if (!entry.version) return undefined;\n return {\n id: entry.id,\n version: entry.version,\n config: { ...values },\n };\n}\n\n/**\n * The `trigger` object, read off the key and fields under `form.triggers`.\n *\n * Identical for both kinds of direct entry: only the create endpoint and what\n * the automation is told to do differ between a prompt and a bundle.\n */\nfunction buildTrigger(\n entry: SetupEntry,\n values: SetupFormValues,\n): SetupRequestBody | undefined {\n const trigger = getTrigger(entry.setup);\n if (!trigger) return undefined;\n\n const properties = TRIGGER_PROPERTIES[trigger.kind];\n const declared = Object.keys(trigger.fields).filter((name) =>\n properties.includes(name),\n );\n const repoPicker = findRepoPickerField(entry.setup);\n\n return {\n type: trigger.kind,\n ...Object.fromEntries(\n declared.map((name) => [name, fieldText(values[name])]),\n ),\n ...(trigger.kind === \"event\" && {\n source: repoPicker?.field.provider ?? \"\",\n ...(entry.setup.filter && {\n filter: interpolateText(entry.setup.filter, {\n form: values,\n automation: entry,\n }),\n }),\n }),\n };\n}\n\n/**\n * The raw create body a bundle entry produces.\n *\n * `tarball_path` is the one value neither declared nor derived: the host packs\n * and uploads the bundle first, and creates from what came back. `template` is\n * the provenance that makes enabling the same entry twice return the\n * automation that already exists rather than a second one.\n *\n * There is no `repos`: the raw endpoint has no such field, and a bundle's\n * script fetches what it needs itself.\n */\nfunction buildBundlePayload(\n entry: SetupEntry,\n values: SetupFormValues,\n tarballPath: string,\n): SetupRequestBody {\n const bundle = entry.setup.bundle!;\n const scope = { form: values, automation: entry };\n\n const payload: SetupRequestBody = {\n name: deriveName(entry, values),\n };\n\n const trigger = buildTrigger(entry, values);\n if (trigger) payload.trigger = trigger;\n\n payload.tarball_path = tarballPath;\n payload.entrypoint = bundle.entrypoint;\n if (bundle.setupScript) payload.setup_script_path = bundle.setupScript;\n if (bundle.timeout !== undefined) payload.timeout = bundle.timeout;\n payload.template = {\n id: entry.id,\n version: bundle.version,\n config: interpolateConfig(bundle.config, scope) as SetupRequestBody,\n };\n\n return payload;\n}\n\n/**\n * Placeholder substitution over the config tree. Only string leaves are\n * templated; a number, a boolean or a null is written through as itself, so an\n * entry can state a value the script reads as the type it expects.\n */\nfunction interpolateConfig(\n node: SetupBundleConfigValue,\n scope: Parameters<typeof interpolateText>[1],\n): SetupBundleConfigValue {\n if (typeof node === \"string\") {\n return interpolateValue(node, scope);\n }\n if (Array.isArray(node)) {\n return node.map((item) => interpolateConfig(item, scope));\n }\n if (typeof node === \"object\" && node !== null) {\n return Object.fromEntries(\n Object.entries(node).map(([key, value]) => [\n key,\n interpolateConfig(value, scope),\n ]),\n );\n }\n return node;\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: automationCreateEndpoint(entry),\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":";;;;AA2CA,SAAgB,EAAyB,GAA4B;CAInE,OAHI,KAAS,EAAc,CAAK,IACvB,EAAsB,cAAc,IAEtC,EAAsB,cAAc;AAC7C;AAGA,SAAgB,IAAmC;CACjD,OAAO,EAAsB,SAAS;AACxC;AAUA,SAAS,EACP,GACQ;CACR,IAAM,IAAO,EAAsB,CAAI;CACvC,IAAI,CAAC,GACH,MAAU,MACR,mDAAmD,EAAK,iFAE1D;CAEF,OAAO;AACT;AAiBA,SAAgB,EAAc,GAA4B;CACxD,OAAO,EAAM,MAAM,SAAS,YAAY,EAAM,MAAM,WAAW,KAAA;AACjE"}
|
|
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 i18n from \"#/i18n\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { findAutomationCommand } from \"#/utils/automation-catalog\";\nimport { getAutomationEndpoint } from \"./automation-interface\";\nimport {\n actionKinds,\n collectFields,\n fieldText,\n fieldValues,\n} from \"./manifest-local-validation\";\nimport { interpolateText, interpolateValue } from \"./manifest-template\";\nimport type {\n SetupActionKind,\n SetupBlock,\n SetupBundleConfigValue,\n SetupEntry,\n SetupFormValues,\n SetupRequestBody,\n SetupTriggerKind,\n} from \"./types\";\n\n/**\n * The creation endpoint a derived draft would be posted to. Resolved on call\n * rather than at import, because the endpoint is the interface manifest's and\n * this module loads whether or not one was admitted.\n *\n * A bundle entry is created through the raw endpoint, because what it sends is\n * a tarball it uploaded rather than arguments to a preset. Called without an\n * entry - as the import path does - it answers for a prompt.\n */\nexport function automationCreateEndpoint(\n entry?: SetupEntry,\n selectedAction?: string | null,\n): string {\n const kind = selectedActionKind(entry, selectedAction);\n if (kind === \"plugin\") return getAutomationEndpoint(\"createPlugin\");\n if (kind === \"upload\" || kind === \"bundle\") {\n return requireBundleEndpoint(\"createBundle\");\n }\n return getAutomationEndpoint(\"createPrompt\");\n}\n\n/** Where a bundle's tarball is uploaded, before the create call. */\nexport function automationUploadEndpoint(): string {\n return requireBundleEndpoint(\"uploads\");\n}\n\n/** The endpoints a bundle entry cannot be created without. */\nconst BUNDLE_ENDPOINTS = [\"createBundle\", \"uploads\"] as const;\n\n/**\n * An endpoint only a bundle needs. The interface manifest may predate bundles,\n * and the host holds no path of its own to fall back to, so this is where that\n * runs out rather than somewhere deep in a request.\n */\nfunction requireBundleEndpoint(\n name: (typeof BUNDLE_ENDPOINTS)[number],\n): string {\n const path = getAutomationEndpoint(name);\n if (!path) {\n throw new Error(\n `The published automation interface declares no '${name}' endpoint, ` +\n \"so this deployment cannot create an automation from a script bundle.\",\n );\n }\n return path;\n}\n\n/**\n * The endpoints this entry needs that the published interface does not declare.\n *\n * Asked before the form renders rather than discovered at the moment of\n * creating: a pinned package that predates bundles can never answer, and the\n * dialog can say so while nothing has been filled in yet. Empty for every\n * entry that is not a bundle, which needs nothing beyond what the block has\n * always declared.\n */\nfunction missingEndpointsForAction(kind: SetupActionKind | \"bundle\"): string[] {\n return kind === \"upload\" || kind === \"bundle\"\n ? BUNDLE_ENDPOINTS.filter((name) => !getAutomationEndpoint(name))\n : [];\n}\n\nexport function missingCreateEndpoints(\n entry: SetupEntry,\n selectedAction?: string | null,\n): string[] {\n if (isBundleEntry(entry)) return missingEndpointsForAction(\"bundle\");\n if (!entry.setup.actions) return [];\n\n if (selectedAction && selectedAction in entry.setup.actions) {\n return missingEndpointsForAction(selectedAction as SetupActionKind);\n }\n\n const missingByAction = actionKinds(entry.setup).map(\n missingEndpointsForAction,\n );\n if (missingByAction.some((missing) => missing.length === 0)) return [];\n return Array.from(new Set(missingByAction.flat()));\n}\n\n/** Whether this entry ships a script tarball instead of a prompt. */\nexport function isBundleEntry(entry: SetupEntry): boolean {\n return entry.setup.mode === \"direct\" && entry.setup.bundle !== undefined;\n}\n\nexport function isUploadAction(\n entry: SetupEntry,\n selectedAction?: string | null,\n): boolean {\n return selectedActionKind(entry, selectedAction) === \"upload\";\n}\n\nexport function selectedActionKind(\n entry: SetupEntry | undefined,\n selectedAction?: string | null,\n): SetupActionKind | \"bundle\" | \"prompt\" {\n if (!entry) return \"prompt\";\n if (isBundleEntry(entry)) return \"bundle\";\n const actions = entry.setup.actions ?? {};\n if (selectedAction && selectedAction in actions) {\n return selectedAction as SetupActionKind;\n }\n const keys = actionKinds(entry.setup);\n if (keys.length === 1) return keys[0];\n return \"prompt\";\n}\n\n/**\n * The `tarball_path` a preflight draft carries.\n *\n * Preflight runs on every field blur and the upload happens once, at submit,\n * so there is no real path to send yet. The service checks this field's scheme\n * at preflight and its ownership only at creation, so a well-formed stand-in\n * validates exactly what preflight is for - the rest of the body - without\n * uploading an archive per keystroke.\n */\nexport const PREFLIGHT_TARBALL_PATH =\n \"oh-internal://uploads/00000000-0000-0000-0000-000000000000\";\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: [\"source\", \"on\"],\n};\n\nconst OPTIONAL_CREATE_PROPERTIES = [\"model\", \"timeout\"] as const;\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(\n setup: SetupBlock,\n selectedAction?: string | null,\n) {\n const match = Object.entries(collectFields(setup, null, selectedAction)).find(\n ([, field]) => field.type === \"repo-picker\",\n );\n return match ? { name: match[0], field: match[1] } : null;\n}\n\n/** Every repository the form collected, whether the picker takes one or many. */\nfunction repositories(\n setup: SetupBlock,\n values: SetupFormValues,\n selectedAction?: string | null,\n): string[] {\n const picker = findRepoPickerField(setup, selectedAction);\n return picker ? fieldValues(values[picker.name]) : [];\n}\n\n/**\n * The created automation's name.\n *\n * One repository is worth naming; several are not, so the count stands in\n * rather than a list of names that would not fit.\n */\nfunction deriveName(\n entry: SetupEntry,\n values: SetupFormValues,\n selectedAction?: string | null,\n): string {\n if (\n \"name\" in collectFields(entry.setup, null, selectedAction) &&\n \"name\" in values\n ) {\n return fieldText(values.name);\n }\n\n const repos = repositories(entry.setup, values, selectedAction);\n if (repos.length === 0) return entry.name;\n if (repos.length === 1) return `${entry.name} - ${repos[0]}`;\n // The count is the one word here the host writes rather than reads off the\n // entry, so it is translated. There is no translator to pass in: the\n // derivation runs from a memo, an upload and a test alike.\n const count = i18n.t(I18nKey.SETUP$REPOSITORY_COUNT, {\n total: repos.length,\n });\n return `${entry.name} - ${count}`;\n}\n\n/** The active trigger kind for a direct entry, with its fields. */\nfunction getTrigger(setup: SetupBlock, selectedTrigger?: string | null) {\n const triggers = setup.form.triggers ?? {};\n const entries = Object.entries(triggers);\n if (entries.length === 0) return null;\n const match =\n selectedTrigger && selectedTrigger in triggers\n ? [selectedTrigger, triggers[selectedTrigger as SetupTriggerKind]]\n : entries.length === 1\n ? entries[0]\n : null;\n if (!match) return null;\n const [kind, fields] = match;\n return { kind: kind as SetupTriggerKind, fields: fields ?? {} };\n}\n\nfunction hasPayloadValue(\n value: SetupRequestBody[string] | undefined,\n): value is SetupRequestBody[string] {\n return value !== undefined && value !== null && value !== \"\";\n}\n\nfunction fieldPayloadValue(\n fieldType: string | undefined,\n value: SetupFormValues[string],\n) {\n if (fieldType === \"number\") {\n const text = fieldText(value);\n return text === \"\" ? undefined : Number(text);\n }\n return fieldText(value);\n}\n\nfunction optionalCreateProperties(\n setup: SetupBlock,\n values: SetupFormValues,\n selectedAction?: string | null,\n): SetupRequestBody {\n return Object.fromEntries(\n OPTIONAL_CREATE_PROPERTIES.flatMap((name) => {\n const field = collectFields(setup, null, selectedAction)[name];\n const value = fieldPayloadValue(field?.type, values[name]);\n return hasPayloadValue(value) ? [[name, value]] : [];\n }),\n ) as SetupRequestBody;\n}\n\nfunction repositoryProperty(\n setup: SetupBlock,\n values: SetupFormValues,\n selectedAction?: string | null,\n): SetupRequestBody {\n const repoPicker = findRepoPickerField(setup, selectedAction);\n const repos = repositories(setup, values, selectedAction);\n if (repos.length === 0 || !repoPicker?.field.provider) return {};\n\n const declared = REPO_PROPERTIES.filter((name) =>\n hasPayloadValue(fieldText(values[name])),\n );\n return {\n repos: repos.map((url) => ({\n url,\n ...Object.fromEntries(\n declared.map((name) => [name, fieldText(values[name])]),\n ),\n provider: repoPicker.field.provider as string,\n })),\n };\n}\n\nfunction parsePluginSources(\n value: SetupRequestBody[string],\n): SetupRequestBody[string] {\n if (!hasPayloadValue(value) || typeof value !== \"string\") return value;\n const trimmed = value.trim();\n if (!trimmed) return [];\n if (trimmed.startsWith(\"[\")) {\n try {\n return JSON.parse(trimmed) as SetupRequestBody[string];\n } catch {\n return [{ source: trimmed }];\n }\n }\n return trimmed\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter(Boolean)\n .map((line) => {\n const [source, ...parts] = line.split(/\\s+/);\n const plugin: Record<string, string> = { source };\n parts.forEach((part) => {\n const [key, ...raw] = part.split(\"=\");\n const valuePart = raw.join(\"=\");\n if ((key === \"ref\" || key === \"repo_path\") && valuePart) {\n plugin[key] = valuePart;\n }\n });\n return plugin;\n });\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 /** Bundle/upload entries only: what the upload returned. */\n tarballPath: string = PREFLIGHT_TARBALL_PATH,\n selectedTrigger?: string | null,\n selectedActionKey?: string | null,\n): SetupRequestBody | null {\n const { setup } = entry;\n if (setup.mode !== \"direct\") return null;\n if (setup.bundle) {\n return buildBundlePayload(entry, values, tarballPath, selectedTrigger);\n }\n if (setup.actions) {\n return buildActionPayload(\n entry,\n values,\n tarballPath,\n selectedTrigger,\n selectedActionKey,\n );\n }\n if (!setup.prompt) return null;\n\n const scope = { form: values, automation: entry };\n\n const payload: SetupRequestBody = {\n name: deriveName(entry, values),\n prompt: interpolateText(setup.prompt, scope),\n ...optionalCreateProperties(setup, values),\n ...repositoryProperty(setup, values),\n };\n\n const trigger = buildTrigger(entry, values, selectedTrigger);\n if (trigger) payload.trigger = trigger;\n\n const template = buildTemplate(entry, values);\n if (template) payload.template = template;\n\n return payload;\n}\n\nfunction buildActionPayload(\n entry: SetupEntry,\n values: SetupFormValues,\n tarballPath: string,\n selectedTrigger?: string | null,\n selectedActionKey?: string | null,\n): SetupRequestBody | null {\n const kind = selectedActionKind(entry, selectedActionKey);\n if (kind === \"bundle\") return null;\n\n const setup = entry.setup;\n const scope = { form: values, automation: entry };\n const payload: SetupRequestBody = {\n name: deriveName(entry, values, selectedActionKey),\n };\n\n const trigger = buildTrigger(\n entry,\n values,\n selectedTrigger,\n selectedActionKey,\n );\n if (trigger) payload.trigger = trigger;\n\n Object.assign(\n payload,\n optionalCreateProperties(setup, values, selectedActionKey),\n );\n\n if (kind === \"upload\") {\n const action = setup.actions?.upload;\n if (!action) return null;\n payload.tarball_path =\n tarballPath || interpolateText(action.tarballPath, scope);\n payload.entrypoint = interpolateText(action.entrypoint, scope);\n if (action.setupScript) {\n const setupScript = interpolateText(action.setupScript, scope);\n if (hasPayloadValue(setupScript)) payload.setup_script_path = setupScript;\n }\n return payload;\n }\n\n const action =\n kind === \"plugin\" ? setup.actions?.plugin : setup.actions?.prompt;\n if (!action) return null;\n payload.prompt = interpolateText(action.prompt, scope);\n Object.assign(payload, repositoryProperty(setup, values, selectedActionKey));\n\n if (kind === \"plugin\") {\n const pluginAction = setup.actions?.plugin;\n if (!pluginAction) return null;\n payload.plugins = parsePluginSources(\n interpolateValue(pluginAction.plugins, scope),\n );\n }\n\n const template = buildTemplate(entry, values);\n if (template) payload.template = template;\n\n return payload;\n}\n\n/**\n * The `template` provenance a prompt entry sends, or undefined for one that\n * publishes no version.\n *\n * It is what lets the service recognise a second setup of the same entry as\n * the automation it already holds. The bundle path derives the same block from\n * the config the bundle declares; a prompt entry declares none, so the answer\n * to \"what was this created from\" is the form as submitted.\n */\nfunction buildTemplate(\n entry: SetupEntry,\n values: SetupFormValues,\n): SetupRequestBody | undefined {\n if (!entry.version) return undefined;\n return {\n id: entry.id,\n version: entry.version,\n config: { ...values } as SetupRequestBody,\n };\n}\n\n/**\n * The `trigger` object, read off the key and fields under `form.triggers`.\n *\n * Identical for both kinds of direct entry: only the create endpoint and what\n * the automation is told to do differ between a prompt and a bundle.\n */\nfunction buildTrigger(\n entry: SetupEntry,\n values: SetupFormValues,\n selectedTrigger?: string | null,\n selectedAction?: string | null,\n): SetupRequestBody | undefined {\n const trigger = getTrigger(entry.setup, selectedTrigger);\n if (!trigger) return undefined;\n\n const properties = TRIGGER_PROPERTIES[trigger.kind];\n const declared = Object.keys(trigger.fields).filter((name) =>\n properties.includes(name),\n );\n const repoPicker = findRepoPickerField(entry.setup, selectedAction);\n const derived = Object.fromEntries(\n declared\n .map((name) => [name, fieldText(values[name])])\n .filter(([, value]) => hasPayloadValue(value)),\n );\n\n const filter = entry.setup.filter\n ? interpolateText(entry.setup.filter, {\n form: values,\n automation: entry,\n })\n : undefined;\n\n return {\n type: trigger.kind,\n ...derived,\n ...(trigger.kind === \"event\" && {\n ...(!(\"source\" in derived) && {\n source: repoPicker?.field.provider ?? \"\",\n }),\n ...(hasPayloadValue(filter) && { filter }),\n }),\n };\n}\n\n/**\n * The raw create body a bundle entry produces.\n *\n * `tarball_path` is the one value neither declared nor derived: the host packs\n * and uploads the bundle first, and creates from what came back. `template` is\n * the provenance that makes enabling the same entry twice return the\n * automation that already exists rather than a second one.\n *\n * There is no `repos`: the raw endpoint has no such field, and a bundle's\n * script fetches what it needs itself.\n */\nfunction buildBundlePayload(\n entry: SetupEntry,\n values: SetupFormValues,\n tarballPath: string,\n selectedTrigger?: string | null,\n): SetupRequestBody {\n const bundle = entry.setup.bundle!;\n const scope = { form: values, automation: entry };\n\n const payload: SetupRequestBody = {\n name: deriveName(entry, values),\n };\n\n const trigger = buildTrigger(entry, values, selectedTrigger);\n if (trigger) payload.trigger = trigger;\n\n payload.tarball_path = tarballPath;\n payload.entrypoint = bundle.entrypoint;\n if (bundle.setupScript) payload.setup_script_path = bundle.setupScript;\n if (bundle.timeout !== undefined) payload.timeout = bundle.timeout;\n payload.template = {\n id: entry.id,\n version: bundle.version,\n config: interpolateConfig(bundle.config, scope) as SetupRequestBody,\n };\n\n return payload;\n}\n\n/**\n * Placeholder substitution over the config tree. Only string leaves are\n * templated; a number, a boolean or a null is written through as itself, so an\n * entry can state a value the script reads as the type it expects.\n */\nfunction interpolateConfig(\n node: SetupBundleConfigValue,\n scope: Parameters<typeof interpolateText>[1],\n): SetupBundleConfigValue {\n if (typeof node === \"string\") {\n return interpolateValue(node, scope);\n }\n if (Array.isArray(node)) {\n return node.map((item) => interpolateConfig(item, scope));\n }\n if (typeof node === \"object\" && node !== null) {\n return Object.fromEntries(\n Object.entries(node).map(([key, value]) => [\n key,\n interpolateConfig(value, scope),\n ]),\n );\n }\n return node;\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 selectedTrigger?: string | null,\n selectedAction?: string | null,\n): SetupRequestBody | null {\n const draft = buildCreatePayload(\n entry,\n values,\n PREFLIGHT_TARBALL_PATH,\n selectedTrigger,\n selectedAction,\n );\n if (!draft) return null;\n\n return {\n automationId: entry.id,\n endpoint: automationCreateEndpoint(entry, selectedAction),\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(\n entry: SetupEntry,\n selectedTrigger?: string | null,\n selectedAction?: string | null,\n): Record<string, string[]> {\n const mapping: Record<string, string[]> = {};\n\n const triggerEntries = Object.keys(entry.setup.form.triggers ?? {});\n const triggerVariants = selectedTrigger\n ? [selectedTrigger]\n : triggerEntries.length > 1\n ? triggerEntries\n : [null];\n\n const actionEntries = actionKinds(entry.setup);\n const actionVariants = selectedAction\n ? [selectedAction]\n : actionEntries.length > 1\n ? actionEntries\n : [null];\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) {\n mapping[path] = Array.from(\n new Set([...(mapping[path] ?? []), ...names]),\n );\n }\n }\n };\n\n actionVariants.forEach((actionVariant) => {\n triggerVariants.forEach((triggerVariant) => {\n const names = Object.keys(\n collectFields(entry.setup, triggerVariant, actionVariant),\n );\n const variantStandIns = Object.fromEntries(\n names.map((name) => [name, `{{form.${name}}}`]),\n );\n const template = buildCreatePayload(\n entry,\n variantStandIns,\n actionVariant === \"upload\" ? \"\" : PREFLIGHT_TARBALL_PATH,\n triggerVariant,\n actionVariant,\n );\n if (template) walk(template, \"\");\n });\n });\n return mapping;\n}\n"],"mappings":";;;;;AA6CA,SAAgB,EACd,GACA,GACQ;CACR,IAAM,IAAO,EAAmB,GAAO,CAAc;CAKrD,OAJI,MAAS,WAAiB,EAAsB,cAAc,IAC9D,MAAS,YAAY,MAAS,WACzB,EAAsB,cAAc,IAEtC,EAAsB,cAAc;AAC7C;AAGA,SAAgB,IAAmC;CACjD,OAAO,EAAsB,SAAS;AACxC;AAUA,SAAS,EACP,GACQ;CACR,IAAM,IAAO,EAAsB,CAAI;CACvC,IAAI,CAAC,GACH,MAAU,MACR,mDAAmD,EAAK,iFAE1D;CAEF,OAAO;AACT;AAoCA,SAAgB,EAAc,GAA4B;CACxD,OAAO,EAAM,MAAM,SAAS,YAAY,EAAM,MAAM,WAAW,KAAA;AACjE;AASA,SAAgB,EACd,GACA,GACuC;CACvC,IAAI,CAAC,GAAO,OAAO;CACnB,IAAI,EAAc,CAAK,GAAG,OAAO;CACjC,IAAM,IAAU,EAAM,MAAM,WAAW,CAAC;CACxC,IAAI,KAAkB,KAAkB,GACtC,OAAO;CAET,IAAM,IAAO,EAAY,EAAM,KAAK;CAEpC,OADI,EAAK,WAAW,IAAU,EAAK,KAC5B;AACT"}
|
|
@@ -17,4 +17,4 @@ export interface SetupActionResult {
|
|
|
17
17
|
/** The created resource, or the conversation that will finish setup. */
|
|
18
18
|
response: Record<string, unknown>;
|
|
19
19
|
}
|
|
20
|
-
export declare function useSetupAction(): (entry: SetupEntry, values: SetupFormValues, payload: SetupRequestBody | null) => Promise<SetupActionResult>;
|
|
20
|
+
export declare function useSetupAction(): (entry: SetupEntry, values: SetupFormValues, payload: SetupRequestBody | null, selectedTrigger?: string | null, selectedAction?: string | null) => Promise<SetupActionResult>;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* what it supports. Neither side is interpreted here beyond set membership, so
|
|
7
7
|
* this stays neutral about what any particular capability means.
|
|
8
8
|
*/
|
|
9
|
-
import type { DeploymentCapabilities, SetupEntry } from "./types";
|
|
9
|
+
import type { DeploymentCapabilities, SetupActionKind, SetupEntry, SetupTriggerKind } from "./types";
|
|
10
10
|
/**
|
|
11
11
|
* "unknown" is a real outcome, not an error: a deployment that cannot be asked
|
|
12
12
|
* must not be treated as one that answered no.
|
|
@@ -22,4 +22,6 @@ export interface SetupCapabilityAssessment {
|
|
|
22
22
|
*/
|
|
23
23
|
unmet: string[];
|
|
24
24
|
}
|
|
25
|
+
export declare function supportedActionKinds(entry: SetupEntry, reported: DeploymentCapabilities): SetupActionKind[];
|
|
26
|
+
export declare function supportedTriggerKinds(entry: SetupEntry, reported: DeploymentCapabilities): SetupTriggerKind[];
|
|
25
27
|
export declare function assessCapabilityRequirements(entry: SetupEntry, reported: DeploymentCapabilities): SetupCapabilityAssessment;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-local-validation.cjs","names":[],"sources":["../../src/manifests/manifest-local-validation.ts"],"sourcesContent":["/**\n * The form model, and the host's own check of what the user typed into it.\n *\n * Local validation is a convenience, not the authority: it is instant and needs\n * no round trip, so it catches empty required fields and obvious typos.\n * Deployment-specific questions (\"is a one-minute schedule allowed here?\")\n * belong to preflight, which is authoritative.\n *\n * Errors are returned as codes rather than sentences so the host can render\n * them through its own translations; only manifest-authored copy is literal.\n */\n\nimport type {\n DeploymentCapabilities,\n SetupActionKind,\n SetupBlock,\n SetupFieldOption,\n SetupFormField,\n SetupFormFields,\n SetupFormValue,\n SetupFormValues,\n} from \"./types\";\n\n/** Characters that would break out of an expression string literal. */\nconst UNSAFE_EXPRESSION_LITERAL_PATTERN = /[\"'\\\\]/;\n\nexport type SetupFieldError =\n | { code: \"required\" }\n | { code: \"minLength\"; length: number }\n | { code: \"maxLength\"; length: number }\n | { code: \"min\"; value: number }\n | { code: \"max\"; value: number }\n | { code: \"invalidOption\" }\n | { code: \"unsafeExpressionLiteral\" };\n\nexport type SetupFieldErrors = Record<string, SetupFieldError>;\n\n/** Field constraints supplied by the deployment rather than by the manifest. */\nexport interface SetupFieldOverride {\n options?: SetupFieldOption[];\n}\n\nexport function triggerKinds(setup: SetupBlock): string[] {\n return Object.keys(setup.form.triggers ?? {});\n}\n\nexport function initialTriggerKind(setup: SetupBlock): string | null {\n return triggerKinds(setup)[0] ?? null;\n}\n\nexport function actionKinds(setup: SetupBlock): SetupActionKind[] {\n return Object.keys(setup.actions ?? {}) as SetupActionKind[];\n}\n\nexport function initialActionKind(setup: SetupBlock): SetupActionKind | null {\n return actionKinds(setup)[0] ?? null;\n}\n\nexport type SetupFieldOverrides = Record<string, SetupFieldOverride>;\n\n/**\n * Every input the form declares, keyed by name, whichever half it is in.\n *\n * Trigger inputs come first so the user is asked when it runs before what it\n * runs on, and so every derived view of the form keeps that order. Admission\n * rejects a name declared in both halves, so the merge cannot lose a field.\n */\nexport function collectFields(\n setup: SetupBlock,\n selectedTrigger?: string | null,\n selectedAction?: string | null,\n): SetupFormFields {\n const triggers = setup.form.triggers ?? {};\n const triggerEntries = Object.entries(triggers);\n const activeTrigger =\n selectedTrigger && selectedTrigger in triggers\n ? triggers[selectedTrigger as keyof typeof triggers]\n : triggerEntries.length === 1\n ? triggerEntries[0][1]\n : {};\n const actions = setup.actions ?? {};\n const actionEntries = Object.entries(actions);\n const activeAction =\n selectedAction && selectedAction in actions\n ? actions[selectedAction as SetupActionKind]?.args\n : actionEntries.length === 1\n ? actionEntries[0][1]?.args\n : {};\n return Object.assign(\n {},\n activeTrigger,\n setup.form.args,\n activeAction,\n ) as SetupFormFields;\n}\n\n/**\n * Feed deployment values into form field constraints, so the form offers only\n * what the deployment accepts.\n *\n * A `timezone` field is the case that needs it: a manifest declares no options\n * for one, because the accepted zones belong to the deployment. The cron\n * interval floor is deliberately left to preflight — the deployment owns that\n * limit and states it in its own words, and a local approximation would\n * pre-empt the authoritative message with a worse one.\n */\nexport function resolveFieldOverrides(\n setup: SetupBlock,\n capabilities: DeploymentCapabilities | null,\n selectedTrigger?: string | null,\n selectedAction?: string | null,\n): SetupFieldOverrides {\n const overrides: SetupFieldOverrides = {};\n const fields = collectFields(setup, selectedTrigger, selectedAction);\n const timezones = capabilities?.triggers?.cron?.timezones;\n if (timezones?.length) {\n const options = timezones.map((zone) => ({ value: zone, label: zone }));\n Object.entries(fields)\n .filter(([, field]) => field.type === \"timezone\")\n .forEach(([name]) => {\n overrides[name] = { options };\n });\n }\n\n if (capabilities?.eventSources?.length) {\n const options = capabilities.eventSources.map((source) => ({\n value: source,\n label: source,\n }));\n Object.entries(fields)\n .filter(([, field]) => field.type === \"event-source\")\n .forEach(([name]) => {\n overrides[name] = { options };\n });\n }\n\n if (capabilities?.eventTypes?.length) {\n const options = capabilities.eventTypes.map((type) => ({\n value: type,\n label: type,\n }));\n Object.entries(fields)\n .filter(([, field]) => field.type === \"event-type\")\n .forEach(([name]) => {\n overrides[name] = { options };\n });\n }\n\n return overrides;\n}\n\n/** The options a field offers, after deployment constraints are applied. */\nexport function getFieldOptions(\n name: string,\n field: SetupFormField,\n overrides: SetupFieldOverrides = {},\n): SetupFieldOption[] {\n return overrides[name]?.options ?? field.options ?? [];\n}\n\n/**\n * Every value a field holds, whether it collects one or many.\n *\n * Reading both shapes as a list is what keeps validation, interpolation and\n * the payload mapping from branching on `multiple` at every use.\n */\nfunction isFileValue(value: SetupFormValue | undefined): value is File {\n return typeof File !== \"undefined\" && value instanceof File;\n}\n\nexport function fieldValues(value: SetupFormValue | undefined): string[] {\n if (Array.isArray(value)) return value.filter((item) => item.trim() !== \"\");\n if (isFileValue(value)) return value.name ? [value.name] : [];\n if (typeof value === \"string\" && value.trim() === \"\") return [];\n if (value === undefined || value === null) return [];\n return [String(value)];\n}\n\n/** The single value a field holds, or \"\" for a field collecting several. */\nexport function fieldText(value: SetupFormValue | undefined): string {\n if (Array.isArray(value) || value === undefined || value === null) return \"\";\n if (isFileValue(value)) return value.name;\n return String(value);\n}\n\n/** Initial form state: every declared field, seeded with its declared default. */\nexport function getInitialFormValues(\n setup: SetupBlock,\n selectedTrigger?: string | null,\n selectedAction?: string | null,\n): SetupFormValues {\n return Object.fromEntries(\n Object.entries(collectFields(setup, selectedTrigger, selectedAction)).map(\n ([name, field]) => [\n name,\n // A field collecting several values starts empty rather than holding one\n // blank entry, so \"required\" means \"add one\" rather than \"fill this in\".\n field.multiple ? [] : (field.default ?? \"\"),\n ],\n ),\n );\n}\n\nfunction validateField(\n name: string,\n field: SetupFormField,\n rawValue: SetupFormValue | undefined,\n overrides: SetupFieldOverrides,\n): SetupFieldError | null {\n const entered = fieldValues(rawValue);\n\n if (entered.length === 0) {\n return field.required ? { code: \"required\" } : null;\n }\n\n // Every entry of a multi-value field answers the same field, so each is held\n // to the same rules and the first failure is the one reported.\n const failures = entered\n .map((item) => validateValue(name, field, item.trim(), overrides))\n .filter((error): error is SetupFieldError => error !== null);\n return failures[0] ?? null;\n}\n\nfunction validateValue(\n name: string,\n field: SetupFormField,\n value: string,\n overrides: SetupFieldOverrides,\n): SetupFieldError | null {\n const { minLength, maxLength, min, max, format } = field.constraints ?? {};\n if (minLength !== undefined && value.length < minLength) {\n return { code: \"minLength\", length: minLength };\n }\n if (maxLength !== undefined && value.length > maxLength) {\n return { code: \"maxLength\", length: maxLength };\n }\n if (field.type === \"number\") {\n const numeric = Number(value);\n if (Number.isNaN(numeric)) return { code: \"invalidOption\" };\n if (min !== undefined && numeric < min) return { code: \"min\", value: min };\n if (max !== undefined && numeric > max) return { code: \"max\", value: max };\n }\n if (\n format === \"safeExpressionLiteral\" &&\n UNSAFE_EXPRESSION_LITERAL_PATTERN.test(value)\n ) {\n return { code: \"unsafeExpressionLiteral\" };\n }\n\n // Any field offering a closed set of values, whether the manifest declared it\n // or the deployment supplied it, must be answered from that set.\n const options = getFieldOptions(name, field, overrides);\n if (options.length > 0 && !options.some((option) => option.value === value)) {\n return { code: \"invalidOption\" };\n }\n\n return null;\n}\n\n/** Check every declared field. Returns only the fields that failed. */\nexport function validateFormValues(\n setup: SetupBlock,\n values: SetupFormValues,\n overrides: SetupFieldOverrides = {},\n selectedTrigger?: string | null,\n selectedAction?: string | null,\n): SetupFieldErrors {\n return Object.entries(\n collectFields(setup, selectedTrigger, selectedAction),\n ).reduce<SetupFieldErrors>((errors, [name, field]) => {\n const error = validateField(name, field, values[name], overrides);\n return error ? { ...errors, [name]: error } : errors;\n }, {});\n}\n"],"mappings":"6CAkDA,SAAgB,EAAY,EAAsC,CAChE,OAAO,OAAO,KAAK,EAAM,SAAW,CAAC,CAAC,CACxC"}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Errors are returned as codes rather than sentences so the host can render
|
|
10
10
|
* them through its own translations; only manifest-authored copy is literal.
|
|
11
11
|
*/
|
|
12
|
-
import type { DeploymentCapabilities, SetupBlock, SetupFieldOption, SetupFormField, SetupFormFields, SetupFormValue, SetupFormValues } from "./types";
|
|
12
|
+
import type { DeploymentCapabilities, SetupActionKind, SetupBlock, SetupFieldOption, SetupFormField, SetupFormFields, SetupFormValue, SetupFormValues } from "./types";
|
|
13
13
|
export type SetupFieldError = {
|
|
14
14
|
code: "required";
|
|
15
15
|
} | {
|
|
@@ -18,6 +18,12 @@ export type SetupFieldError = {
|
|
|
18
18
|
} | {
|
|
19
19
|
code: "maxLength";
|
|
20
20
|
length: number;
|
|
21
|
+
} | {
|
|
22
|
+
code: "min";
|
|
23
|
+
value: number;
|
|
24
|
+
} | {
|
|
25
|
+
code: "max";
|
|
26
|
+
value: number;
|
|
21
27
|
} | {
|
|
22
28
|
code: "invalidOption";
|
|
23
29
|
} | {
|
|
@@ -28,6 +34,10 @@ export type SetupFieldErrors = Record<string, SetupFieldError>;
|
|
|
28
34
|
export interface SetupFieldOverride {
|
|
29
35
|
options?: SetupFieldOption[];
|
|
30
36
|
}
|
|
37
|
+
export declare function triggerKinds(setup: SetupBlock): string[];
|
|
38
|
+
export declare function initialTriggerKind(setup: SetupBlock): string | null;
|
|
39
|
+
export declare function actionKinds(setup: SetupBlock): SetupActionKind[];
|
|
40
|
+
export declare function initialActionKind(setup: SetupBlock): SetupActionKind | null;
|
|
31
41
|
export type SetupFieldOverrides = Record<string, SetupFieldOverride>;
|
|
32
42
|
/**
|
|
33
43
|
* Every input the form declares, keyed by name, whichever half it is in.
|
|
@@ -36,7 +46,7 @@ export type SetupFieldOverrides = Record<string, SetupFieldOverride>;
|
|
|
36
46
|
* runs on, and so every derived view of the form keeps that order. Admission
|
|
37
47
|
* rejects a name declared in both halves, so the merge cannot lose a field.
|
|
38
48
|
*/
|
|
39
|
-
export declare function collectFields(setup: SetupBlock): SetupFormFields;
|
|
49
|
+
export declare function collectFields(setup: SetupBlock, selectedTrigger?: string | null, selectedAction?: string | null): SetupFormFields;
|
|
40
50
|
/**
|
|
41
51
|
* Feed deployment values into form field constraints, so the form offers only
|
|
42
52
|
* what the deployment accepts.
|
|
@@ -47,19 +57,13 @@ export declare function collectFields(setup: SetupBlock): SetupFormFields;
|
|
|
47
57
|
* limit and states it in its own words, and a local approximation would
|
|
48
58
|
* pre-empt the authoritative message with a worse one.
|
|
49
59
|
*/
|
|
50
|
-
export declare function resolveFieldOverrides(setup: SetupBlock, capabilities: DeploymentCapabilities | null): SetupFieldOverrides;
|
|
60
|
+
export declare function resolveFieldOverrides(setup: SetupBlock, capabilities: DeploymentCapabilities | null, selectedTrigger?: string | null, selectedAction?: string | null): SetupFieldOverrides;
|
|
51
61
|
/** The options a field offers, after deployment constraints are applied. */
|
|
52
62
|
export declare function getFieldOptions(name: string, field: SetupFormField, overrides?: SetupFieldOverrides): SetupFieldOption[];
|
|
53
|
-
/**
|
|
54
|
-
* Every value a field holds, whether it collects one or many.
|
|
55
|
-
*
|
|
56
|
-
* Reading both shapes as a list is what keeps validation, interpolation and
|
|
57
|
-
* the payload mapping from branching on `multiple` at every use.
|
|
58
|
-
*/
|
|
59
63
|
export declare function fieldValues(value: SetupFormValue | undefined): string[];
|
|
60
64
|
/** The single value a field holds, or "" for a field collecting several. */
|
|
61
65
|
export declare function fieldText(value: SetupFormValue | undefined): string;
|
|
62
66
|
/** Initial form state: every declared field, seeded with its declared default. */
|
|
63
|
-
export declare function getInitialFormValues(setup: SetupBlock): SetupFormValues;
|
|
67
|
+
export declare function getInitialFormValues(setup: SetupBlock, selectedTrigger?: string | null, selectedAction?: string | null): SetupFormValues;
|
|
64
68
|
/** Check every declared field. Returns only the fields that failed. */
|
|
65
|
-
export declare function validateFormValues(setup: SetupBlock, values: SetupFormValues, overrides?: SetupFieldOverrides): SetupFieldErrors;
|
|
69
|
+
export declare function validateFormValues(setup: SetupBlock, values: SetupFormValues, overrides?: SetupFieldOverrides, selectedTrigger?: string | null, selectedAction?: string | null): SetupFieldErrors;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-local-validation.js","names":[],"sources":["../../src/manifests/manifest-local-validation.ts"],"sourcesContent":["/**\n * The form model, and the host's own check of what the user typed into it.\n *\n * Local validation is a convenience, not the authority: it is instant and needs\n * no round trip, so it catches empty required fields and obvious typos.\n * Deployment-specific questions (\"is a one-minute schedule allowed here?\")\n * belong to preflight, which is authoritative.\n *\n * Errors are returned as codes rather than sentences so the host can render\n * them through its own translations; only manifest-authored copy is literal.\n */\n\nimport type {\n DeploymentCapabilities,\n SetupActionKind,\n SetupBlock,\n SetupFieldOption,\n SetupFormField,\n SetupFormFields,\n SetupFormValue,\n SetupFormValues,\n} from \"./types\";\n\n/** Characters that would break out of an expression string literal. */\nconst UNSAFE_EXPRESSION_LITERAL_PATTERN = /[\"'\\\\]/;\n\nexport type SetupFieldError =\n | { code: \"required\" }\n | { code: \"minLength\"; length: number }\n | { code: \"maxLength\"; length: number }\n | { code: \"min\"; value: number }\n | { code: \"max\"; value: number }\n | { code: \"invalidOption\" }\n | { code: \"unsafeExpressionLiteral\" };\n\nexport type SetupFieldErrors = Record<string, SetupFieldError>;\n\n/** Field constraints supplied by the deployment rather than by the manifest. */\nexport interface SetupFieldOverride {\n options?: SetupFieldOption[];\n}\n\nexport function triggerKinds(setup: SetupBlock): string[] {\n return Object.keys(setup.form.triggers ?? {});\n}\n\nexport function initialTriggerKind(setup: SetupBlock): string | null {\n return triggerKinds(setup)[0] ?? null;\n}\n\nexport function actionKinds(setup: SetupBlock): SetupActionKind[] {\n return Object.keys(setup.actions ?? {}) as SetupActionKind[];\n}\n\nexport function initialActionKind(setup: SetupBlock): SetupActionKind | null {\n return actionKinds(setup)[0] ?? null;\n}\n\nexport type SetupFieldOverrides = Record<string, SetupFieldOverride>;\n\n/**\n * Every input the form declares, keyed by name, whichever half it is in.\n *\n * Trigger inputs come first so the user is asked when it runs before what it\n * runs on, and so every derived view of the form keeps that order. Admission\n * rejects a name declared in both halves, so the merge cannot lose a field.\n */\nexport function collectFields(\n setup: SetupBlock,\n selectedTrigger?: string | null,\n selectedAction?: string | null,\n): SetupFormFields {\n const triggers = setup.form.triggers ?? {};\n const triggerEntries = Object.entries(triggers);\n const activeTrigger =\n selectedTrigger && selectedTrigger in triggers\n ? triggers[selectedTrigger as keyof typeof triggers]\n : triggerEntries.length === 1\n ? triggerEntries[0][1]\n : {};\n const actions = setup.actions ?? {};\n const actionEntries = Object.entries(actions);\n const activeAction =\n selectedAction && selectedAction in actions\n ? actions[selectedAction as SetupActionKind]?.args\n : actionEntries.length === 1\n ? actionEntries[0][1]?.args\n : {};\n return Object.assign(\n {},\n activeTrigger,\n setup.form.args,\n activeAction,\n ) as SetupFormFields;\n}\n\n/**\n * Feed deployment values into form field constraints, so the form offers only\n * what the deployment accepts.\n *\n * A `timezone` field is the case that needs it: a manifest declares no options\n * for one, because the accepted zones belong to the deployment. The cron\n * interval floor is deliberately left to preflight — the deployment owns that\n * limit and states it in its own words, and a local approximation would\n * pre-empt the authoritative message with a worse one.\n */\nexport function resolveFieldOverrides(\n setup: SetupBlock,\n capabilities: DeploymentCapabilities | null,\n selectedTrigger?: string | null,\n selectedAction?: string | null,\n): SetupFieldOverrides {\n const overrides: SetupFieldOverrides = {};\n const fields = collectFields(setup, selectedTrigger, selectedAction);\n const timezones = capabilities?.triggers?.cron?.timezones;\n if (timezones?.length) {\n const options = timezones.map((zone) => ({ value: zone, label: zone }));\n Object.entries(fields)\n .filter(([, field]) => field.type === \"timezone\")\n .forEach(([name]) => {\n overrides[name] = { options };\n });\n }\n\n if (capabilities?.eventSources?.length) {\n const options = capabilities.eventSources.map((source) => ({\n value: source,\n label: source,\n }));\n Object.entries(fields)\n .filter(([, field]) => field.type === \"event-source\")\n .forEach(([name]) => {\n overrides[name] = { options };\n });\n }\n\n if (capabilities?.eventTypes?.length) {\n const options = capabilities.eventTypes.map((type) => ({\n value: type,\n label: type,\n }));\n Object.entries(fields)\n .filter(([, field]) => field.type === \"event-type\")\n .forEach(([name]) => {\n overrides[name] = { options };\n });\n }\n\n return overrides;\n}\n\n/** The options a field offers, after deployment constraints are applied. */\nexport function getFieldOptions(\n name: string,\n field: SetupFormField,\n overrides: SetupFieldOverrides = {},\n): SetupFieldOption[] {\n return overrides[name]?.options ?? field.options ?? [];\n}\n\n/**\n * Every value a field holds, whether it collects one or many.\n *\n * Reading both shapes as a list is what keeps validation, interpolation and\n * the payload mapping from branching on `multiple` at every use.\n */\nfunction isFileValue(value: SetupFormValue | undefined): value is File {\n return typeof File !== \"undefined\" && value instanceof File;\n}\n\nexport function fieldValues(value: SetupFormValue | undefined): string[] {\n if (Array.isArray(value)) return value.filter((item) => item.trim() !== \"\");\n if (isFileValue(value)) return value.name ? [value.name] : [];\n if (typeof value === \"string\" && value.trim() === \"\") return [];\n if (value === undefined || value === null) return [];\n return [String(value)];\n}\n\n/** The single value a field holds, or \"\" for a field collecting several. */\nexport function fieldText(value: SetupFormValue | undefined): string {\n if (Array.isArray(value) || value === undefined || value === null) return \"\";\n if (isFileValue(value)) return value.name;\n return String(value);\n}\n\n/** Initial form state: every declared field, seeded with its declared default. */\nexport function getInitialFormValues(\n setup: SetupBlock,\n selectedTrigger?: string | null,\n selectedAction?: string | null,\n): SetupFormValues {\n return Object.fromEntries(\n Object.entries(collectFields(setup, selectedTrigger, selectedAction)).map(\n ([name, field]) => [\n name,\n // A field collecting several values starts empty rather than holding one\n // blank entry, so \"required\" means \"add one\" rather than \"fill this in\".\n field.multiple ? [] : (field.default ?? \"\"),\n ],\n ),\n );\n}\n\nfunction validateField(\n name: string,\n field: SetupFormField,\n rawValue: SetupFormValue | undefined,\n overrides: SetupFieldOverrides,\n): SetupFieldError | null {\n const entered = fieldValues(rawValue);\n\n if (entered.length === 0) {\n return field.required ? { code: \"required\" } : null;\n }\n\n // Every entry of a multi-value field answers the same field, so each is held\n // to the same rules and the first failure is the one reported.\n const failures = entered\n .map((item) => validateValue(name, field, item.trim(), overrides))\n .filter((error): error is SetupFieldError => error !== null);\n return failures[0] ?? null;\n}\n\nfunction validateValue(\n name: string,\n field: SetupFormField,\n value: string,\n overrides: SetupFieldOverrides,\n): SetupFieldError | null {\n const { minLength, maxLength, min, max, format } = field.constraints ?? {};\n if (minLength !== undefined && value.length < minLength) {\n return { code: \"minLength\", length: minLength };\n }\n if (maxLength !== undefined && value.length > maxLength) {\n return { code: \"maxLength\", length: maxLength };\n }\n if (field.type === \"number\") {\n const numeric = Number(value);\n if (Number.isNaN(numeric)) return { code: \"invalidOption\" };\n if (min !== undefined && numeric < min) return { code: \"min\", value: min };\n if (max !== undefined && numeric > max) return { code: \"max\", value: max };\n }\n if (\n format === \"safeExpressionLiteral\" &&\n UNSAFE_EXPRESSION_LITERAL_PATTERN.test(value)\n ) {\n return { code: \"unsafeExpressionLiteral\" };\n }\n\n // Any field offering a closed set of values, whether the manifest declared it\n // or the deployment supplied it, must be answered from that set.\n const options = getFieldOptions(name, field, overrides);\n if (options.length > 0 && !options.some((option) => option.value === value)) {\n return { code: \"invalidOption\" };\n }\n\n return null;\n}\n\n/** Check every declared field. Returns only the fields that failed. */\nexport function validateFormValues(\n setup: SetupBlock,\n values: SetupFormValues,\n overrides: SetupFieldOverrides = {},\n selectedTrigger?: string | null,\n selectedAction?: string | null,\n): SetupFieldErrors {\n return Object.entries(\n collectFields(setup, selectedTrigger, selectedAction),\n ).reduce<SetupFieldErrors>((errors, [name, field]) => {\n const error = validateField(name, field, values[name], overrides);\n return error ? { ...errors, [name]: error } : errors;\n }, {});\n}\n"],"mappings":";AAkDA,SAAgB,EAAY,GAAsC;CAChE,OAAO,OAAO,KAAK,EAAM,WAAW,CAAC,CAAC;AACxC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
require("../_virtual/_rolldown/runtime.cjs");var e=/\{\{([A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)*)\}\}/g;function t(e){return typeof e==`string`?e:typeof e==`number`||typeof e==`boolean`?String(e):Array.isArray(e)?e.map(t).filter(Boolean).join(`, `):``}function n(n,r){return n.replace(e,(e,n)=>t(r[n]))}exports.interpolateValues=n;
|
|
2
|
+
//# sourceMappingURL=manifest-template.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-template.cjs","names":[],"sources":["../../src/manifests/manifest-template.ts"],"sourcesContent":["/**\n * Placeholder interpolation for setup copy and request-body strings.\n *\n * A setup block declares what the user sees and the two request strings the\n * host cannot derive as templates over a small set of namespaces. Interpolation\n * is plain substitution — there is no expression language, so a setup block\n * cannot express behavior here.\n */\n\nimport type { SetupEntry, SetupFormValues, SetupPayloadValue } from \"./types\";\n\nconst PLACEHOLDER_PATTERN = /\\{\\{([A-Za-z0-9_]+(?:\\.[A-Za-z0-9_]+)*)\\}\\}/g;\n\nexport interface SetupScope {\n form?: SetupFormValues;\n /** The catalog entry the setup block belongs to. */\n automation?: SetupEntry;\n}\n\n/** Walk a dotted path through plain objects. */\nexport function getByPath(source: unknown, path: string): unknown {\n return path.split(\".\").reduce<unknown>((current, segment) => {\n if (typeof current !== \"object\" || current === null) return undefined;\n return (current as Record<string, unknown>)[segment];\n }, source);\n}\n\nfunction toText(value: unknown): string {\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return String(value);\n }\n // A multi-value field inside a sentence reads as a list of names.\n if (Array.isArray(value)) return value.map(toText).filter(Boolean).join(\", \");\n // Missing values render as blank; callers that care show their own fallback.\n return \"\";\n}\n\n/** A template that is exactly one placeholder, or null if it is not. */\nfunction wholePlaceholderPath(template: string): string | null {\n const match = /^\\{\\{([A-Za-z0-9_]+(?:\\.[A-Za-z0-9_]+)*)\\}\\}$/.exec(template);\n return match ? match[1] : null;\n}\n\nfunction isPlainPayloadValue(value: unknown): value is SetupPayloadValue {\n if (\n value === null ||\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n return true;\n }\n if (Array.isArray(value)) return value.every(isPlainPayloadValue);\n if (typeof value !== \"object\") return false;\n if (Object.getPrototypeOf(value) !== Object.prototype) return false;\n return Object.values(value).every(isPlainPayloadValue);\n}\n\n/**\n * Substitute placeholders, keeping the resolved value's own type when the\n * template is nothing but that placeholder.\n *\n * This is what lets a request body state `\"repos\": \"{{form.repositories}}\"` and\n * get an array. Inside a sentence the same placeholder still reads as text,\n * because there is nowhere for a list to go in a string.\n *\n * Plain JSON form values are kept whole. A placeholder naming anything else -\n * for example `{{automation.setup}}`, or a browser `File` selected by the user -\n * reads as text like it does inside a sentence, so a manifest cannot state one\n * value and put its own object graph into the request body.\n */\nexport function interpolateValue(\n template: string,\n scope: SetupScope,\n): SetupPayloadValue {\n const path = wholePlaceholderPath(template);\n if (path === null) return interpolateText(template, scope);\n const resolved = getByPath(scope, path);\n return isPlainPayloadValue(resolved) ? resolved : toText(resolved);\n}\n\n/** Substitute placeholders inside a template string. */\nexport function interpolateText(template: string, scope: SetupScope): string {\n return template.replace(PLACEHOLDER_PATTERN, (_match, path: string) =>\n toText(getByPath(scope, path)),\n );\n}\n\n/** Substitute placeholders from a flat name → value record. */\nexport function interpolateValues(\n template: string,\n values: Record<string, string | number>,\n): string {\n return template.replace(PLACEHOLDER_PATTERN, (_match, name: string) =>\n toText(values[name]),\n );\n}\n"],"mappings":"6CAWA,IAAM,EAAsB,+CAgB5B,SAAS,EAAO,EAAwB,CAQtC,OAPI,OAAO,GAAU,SAAiB,EAClC,OAAO,GAAU,UAAY,OAAO,GAAU,UACzC,OAAO,CAAK,EAGjB,MAAM,QAAQ,CAAK,EAAU,EAAM,IAAI,CAAM,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI,EAErE,EACT,CAsDA,SAAgB,EACd,EACA,EACQ,CACR,OAAO,EAAS,QAAQ,GAAsB,EAAQ,IACpD,EAAO,EAAO,EAAK,CACrB,CACF"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* is plain substitution — there is no expression language, so a setup block
|
|
7
7
|
* cannot express behavior here.
|
|
8
8
|
*/
|
|
9
|
-
import type { SetupEntry, SetupFormValues } from "./types";
|
|
9
|
+
import type { SetupEntry, SetupFormValues, SetupPayloadValue } from "./types";
|
|
10
10
|
export interface SetupScope {
|
|
11
11
|
form?: SetupFormValues;
|
|
12
12
|
/** The catalog entry the setup block belongs to. */
|
|
@@ -22,12 +22,12 @@ export declare function getByPath(source: unknown, path: string): unknown;
|
|
|
22
22
|
* get an array. Inside a sentence the same placeholder still reads as text,
|
|
23
23
|
* because there is nowhere for a list to go in a string.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
* Plain JSON form values are kept whole. A placeholder naming anything else -
|
|
26
|
+
* for example `{{automation.setup}}`, or a browser `File` selected by the user -
|
|
27
27
|
* reads as text like it does inside a sentence, so a manifest cannot state one
|
|
28
28
|
* value and put its own object graph into the request body.
|
|
29
29
|
*/
|
|
30
|
-
export declare function interpolateValue(template: string, scope: SetupScope):
|
|
30
|
+
export declare function interpolateValue(template: string, scope: SetupScope): SetupPayloadValue;
|
|
31
31
|
/** Substitute placeholders inside a template string. */
|
|
32
32
|
export declare function interpolateText(template: string, scope: SetupScope): string;
|
|
33
33
|
/** Substitute placeholders from a flat name → value record. */
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/manifests/manifest-template.ts
|
|
2
|
+
var e = /\{\{([A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)*)\}\}/g;
|
|
3
|
+
function t(e) {
|
|
4
|
+
return typeof e == "string" ? e : typeof e == "number" || typeof e == "boolean" ? String(e) : Array.isArray(e) ? e.map(t).filter(Boolean).join(", ") : "";
|
|
5
|
+
}
|
|
6
|
+
function n(n, r) {
|
|
7
|
+
return n.replace(e, (e, n) => t(r[n]));
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { n as interpolateValues };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=manifest-template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-template.js","names":[],"sources":["../../src/manifests/manifest-template.ts"],"sourcesContent":["/**\n * Placeholder interpolation for setup copy and request-body strings.\n *\n * A setup block declares what the user sees and the two request strings the\n * host cannot derive as templates over a small set of namespaces. Interpolation\n * is plain substitution — there is no expression language, so a setup block\n * cannot express behavior here.\n */\n\nimport type { SetupEntry, SetupFormValues, SetupPayloadValue } from \"./types\";\n\nconst PLACEHOLDER_PATTERN = /\\{\\{([A-Za-z0-9_]+(?:\\.[A-Za-z0-9_]+)*)\\}\\}/g;\n\nexport interface SetupScope {\n form?: SetupFormValues;\n /** The catalog entry the setup block belongs to. */\n automation?: SetupEntry;\n}\n\n/** Walk a dotted path through plain objects. */\nexport function getByPath(source: unknown, path: string): unknown {\n return path.split(\".\").reduce<unknown>((current, segment) => {\n if (typeof current !== \"object\" || current === null) return undefined;\n return (current as Record<string, unknown>)[segment];\n }, source);\n}\n\nfunction toText(value: unknown): string {\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return String(value);\n }\n // A multi-value field inside a sentence reads as a list of names.\n if (Array.isArray(value)) return value.map(toText).filter(Boolean).join(\", \");\n // Missing values render as blank; callers that care show their own fallback.\n return \"\";\n}\n\n/** A template that is exactly one placeholder, or null if it is not. */\nfunction wholePlaceholderPath(template: string): string | null {\n const match = /^\\{\\{([A-Za-z0-9_]+(?:\\.[A-Za-z0-9_]+)*)\\}\\}$/.exec(template);\n return match ? match[1] : null;\n}\n\nfunction isPlainPayloadValue(value: unknown): value is SetupPayloadValue {\n if (\n value === null ||\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n return true;\n }\n if (Array.isArray(value)) return value.every(isPlainPayloadValue);\n if (typeof value !== \"object\") return false;\n if (Object.getPrototypeOf(value) !== Object.prototype) return false;\n return Object.values(value).every(isPlainPayloadValue);\n}\n\n/**\n * Substitute placeholders, keeping the resolved value's own type when the\n * template is nothing but that placeholder.\n *\n * This is what lets a request body state `\"repos\": \"{{form.repositories}}\"` and\n * get an array. Inside a sentence the same placeholder still reads as text,\n * because there is nowhere for a list to go in a string.\n *\n * Plain JSON form values are kept whole. A placeholder naming anything else -\n * for example `{{automation.setup}}`, or a browser `File` selected by the user -\n * reads as text like it does inside a sentence, so a manifest cannot state one\n * value and put its own object graph into the request body.\n */\nexport function interpolateValue(\n template: string,\n scope: SetupScope,\n): SetupPayloadValue {\n const path = wholePlaceholderPath(template);\n if (path === null) return interpolateText(template, scope);\n const resolved = getByPath(scope, path);\n return isPlainPayloadValue(resolved) ? resolved : toText(resolved);\n}\n\n/** Substitute placeholders inside a template string. */\nexport function interpolateText(template: string, scope: SetupScope): string {\n return template.replace(PLACEHOLDER_PATTERN, (_match, path: string) =>\n toText(getByPath(scope, path)),\n );\n}\n\n/** Substitute placeholders from a flat name → value record. */\nexport function interpolateValues(\n template: string,\n values: Record<string, string | number>,\n): string {\n return template.replace(PLACEHOLDER_PATTERN, (_match, name: string) =>\n toText(values[name]),\n );\n}\n"],"mappings":";AAWA,IAAM,IAAsB;AAgB5B,SAAS,EAAO,GAAwB;CAQtC,OAPI,OAAO,KAAU,WAAiB,IAClC,OAAO,KAAU,YAAY,OAAO,KAAU,YACzC,OAAO,CAAK,IAGjB,MAAM,QAAQ,CAAK,IAAU,EAAM,IAAI,CAAM,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI,IAErE;AACT;AAsDA,SAAgB,EACd,GACA,GACQ;CACR,OAAO,EAAS,QAAQ,IAAsB,GAAQ,MACpD,EAAO,EAAO,EAAK,CACrB;AACF"}
|