@openhands/agent-canvas 1.2.1 → 1.3.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/{Trans-De9RwQhA.js → Trans-DY6toRDu.js} +1 -1
- package/build/assets/{acp-providers-D_D8VUpP.js → acp-providers-Djl6vNn-.js} +1 -1
- package/build/assets/active-backend-context-B3umL5R-.js +1 -0
- package/build/assets/{add-backend-modal-DIEyAkA4.js → add-backend-modal-DljoP9lc.js} +1 -1
- package/build/assets/{agent-profiles-settings-BovdV4N8.js → agent-profiles-settings-BJMmTZgv.js} +1 -1
- package/build/assets/{agent-server-conversation-service.api-BP73yONA.js → agent-server-conversation-service.api-D_ZvtJjG.js} +3 -3
- package/build/assets/agent-settings-BVVFzBWS.js +1 -0
- package/build/assets/{agent-settings-Ofbh6UTD.js → agent-settings-lUknCeaF.js} +1 -1
- package/build/assets/{alert-banner-BW0wRYw6.js → alert-banner-Bpf2NQJq.js} +1 -1
- package/build/assets/{api-key-entry-screen-CZIdRmUJ.js → api-key-entry-screen-CWEHcPJA.js} +1 -1
- package/build/assets/{app-settings-BHJ9nKti.js → app-settings-Dj90qosy.js} +1 -1
- package/build/assets/automation-detail-CXJU0_gF.js +2 -0
- package/build/assets/automation-export-FWY9LPsG.js +2 -0
- package/build/assets/automations-list-DDGBkWLC.js +1 -0
- package/build/assets/{back-nav-button-BYmxFxGt.js → back-nav-button-CUS2mi-U.js} +1 -1
- package/build/assets/{backend-form-modal-BiaOB2C4.js → backend-form-modal-DSbrafX2.js} +1 -1
- package/build/assets/{backend-synced-settings-badge-Bs3FSXsS.js → backend-synced-settings-badge-CanzmG12.js} +1 -1
- package/build/assets/{base-modal-CC5Pp8CF.js → base-modal-BuF2f0YL.js} +1 -1
- package/build/assets/{brand-button-DKPMEKcW.js → brand-button-vMVuyDXu.js} +1 -1
- package/build/assets/{browser-B39N74jU.js → browser-BifzWIBd.js} +1 -1
- package/build/assets/{browser-tab-aiDg7aEn.js → browser-tab-CkkqJnqw.js} +1 -1
- package/build/assets/{chat-send-button-DxuZIoW8.js → chat-send-button-B_jc5z5C.js} +1 -1
- package/build/assets/{circle-plus-check-toggle-T3eAIuPQ.js → circle-plus-check-toggle-C5YxjxvI.js} +1 -1
- package/build/assets/{combobox-caret-CLGvOs32.js → combobox-caret-DoObzqf4.js} +1 -1
- package/build/assets/{command-menu-DKrLCRla.js → command-menu-D-JV3Wqm.js} +1 -1
- package/build/assets/{condenser-settings-D_5vY4m3.js → condenser-settings-30cX9SK6.js} +1 -1
- package/build/assets/{confirmation-modal-BK1F0A68.js → confirmation-modal-BGLu6XJN.js} +1 -1
- package/build/assets/{context-menu-list-item-DBFzplWW.js → context-menu-list-item-BD5_EaKN.js} +1 -1
- package/build/assets/conversation-BltULtFA.js +89 -0
- package/build/assets/conversation-e0a0_X4S.js +1 -0
- package/build/assets/conversation-panel-Cr9_ZGI-.js +1 -0
- package/build/assets/{conversation-service.api-CqZcHr1c.js → conversation-service.api-DQaKzIRj.js} +1 -1
- package/build/assets/{conversation-tab-empty-state-m8IUkfMj.js → conversation-tab-empty-state-B2dDYEwf.js} +1 -1
- package/build/assets/{copy-to-clipboard-button-Aj9wFmt2.js → copy-to-clipboard-button-CaR52OnA.js} +1 -1
- package/build/assets/declaration-gq8V5eTU.js +1 -0
- package/build/assets/{device-verify-B5YCrvSu.js → device-verify-BsznkV-a.js} +1 -1
- package/build/assets/{dropdown-classes-CgQ45JmQ.js → dropdown-classes-DJSXeLqc.js} +1 -1
- package/build/assets/{ellipsis-button-1BV9Sga8.js → ellipsis-button-C5Z-tPFp.js} +1 -1
- package/build/assets/{entry.client-Dy--3GeA.js → entry.client-JixNvFrr.js} +2 -2
- package/build/assets/{enum-filter-dropdown-DCITiYVi.js → enum-filter-dropdown-DXbVwiNZ.js} +1 -1
- package/build/assets/{environment-switch-overlay-DUnKILNH.js → environment-switch-overlay-DZQfRGTx.js} +1 -1
- package/build/assets/{extensions-hub-CksKNpP9.js → extensions-hub-DfvgqzTc.js} +1 -1
- package/build/assets/{extensions-navigation-Di9rJ-b5.js → extensions-navigation-D9i0DL62.js} +1 -1
- package/build/assets/files-tab-DDtEJcGE.js +1 -0
- package/build/assets/{form-control-classes-9rkU4usZ.js → form-control-classes-ChpNBe9n.js} +1 -1
- package/build/assets/{git-provider-icon-aQRcUVI-.js → git-provider-icon-BjkecAuV.js} +1 -1
- package/build/assets/home-BCKWpKpf.js +1 -0
- package/build/assets/{install-server-modal-BRab9inF.js → install-server-modal-BIugLhVB.js} +1 -1
- package/build/assets/{launch-1TJRKkkX.js → launch-BgIlZLSX.js} +1 -1
- package/build/assets/lesson-plan-DgpP80mJ.js +4 -0
- package/build/assets/{llm-not-configured-banner-BrU5uHto.js → llm-not-configured-banner-BlX8NifC.js} +4 -4
- package/build/assets/llm-settings-DJmbryaE.js +1 -0
- package/build/assets/{llm-settings-BpdU51Jo.js → llm-settings-kU5v0H1Z.js} +1 -1
- package/build/assets/{loading-spinner-DHAIPZkP.js → loading-spinner-BLdswi6q.js} +1 -1
- package/build/assets/{manage-backends-modal-BREySP-J.js → manage-backends-modal-C8FJQIZR.js} +1 -1
- package/build/assets/manifest-56d40d7c.js +1 -0
- package/build/assets/{markdown-renderer-D8mayGjI.js → markdown-renderer-DMT7Zkpd.js} +1 -1
- package/build/assets/{mcp-XXf8k6mb.js → mcp-CHkklDMv.js} +1 -1
- package/build/assets/{messages-D2oZb47Z.js → messages-B3RugH7P.js} +11 -11
- package/build/assets/{modal-body-Dcu5McCa.js → modal-body-DouOE5KH.js} +1 -1
- package/build/assets/{modal-classes-DPqxSyuP.js → modal-classes-Cf-4tbHz.js} +1 -1
- package/build/assets/{modal-close-button-CTCX4nCH.js → modal-close-button-B6CfxxJj.js} +1 -1
- package/build/assets/{model-selector-VDHBytb6.js → model-selector-BOj1wBlq.js} +1 -1
- package/build/assets/onboarding-DYhDxmI9.js +1 -0
- package/build/assets/{onboarding-modal-C084vkv7.js → onboarding-modal-CP_WxDC9.js} +1 -1
- package/build/assets/{option-service.api-Cl5VLGyw.js → option-service.api-C0tYT4-r.js} +1 -1
- package/build/assets/{path-utils-BeOmjIcE.js → path-utils-DWcgCGen.js} +1 -1
- package/build/assets/{plan-components-j9X4AxYM.js → plan-components-CcCBmCzW.js} +1 -1
- package/build/assets/{planner-tab-AZMjJx31.js → planner-tab-CCqdXxkL.js} +1 -1
- package/build/assets/providers-BxCuC9bZ.js +1 -0
- package/build/assets/{recommended-automations-launcher-Bk8tQ-Cn.js → recommended-automations-launcher-CVEF_1pc.js} +1 -1
- package/build/assets/root-V8fkplMy.css +1 -0
- package/build/assets/root-layout-CttG_lQi.js +2 -0
- package/build/assets/{root-tuEPDx-i.js → root-uJ2t9rCQ.js} +2 -2
- package/build/assets/{runtime-waiting-state-DkzLgvMn.js → runtime-waiting-state-BsXLElvN.js} +1 -1
- package/build/assets/{schema-field-nvq_Juo2.js → schema-field-xUr6rDd6.js} +1 -1
- package/build/assets/{sdk-section-page-CgT70FaZ.js → sdk-section-page-CbLrVUa3.js} +1 -1
- package/build/assets/{sdk-settings-schema-BjBkMHzZ.js → sdk-settings-schema-Cv7M9eSe.js} +1 -1
- package/build/assets/{secrets-settings-BCxk9p9s.js → secrets-settings-DxTx9HgU.js} +1 -1
- package/build/assets/{segmented-toggle-Cen5AMUc.js → segmented-toggle-DyQqPhoD.js} +1 -1
- package/build/assets/{settings-CedTdtb5.js → settings-DnlfS09K.js} +1 -1
- package/build/assets/{settings-dropdown-input-xzoZwgUe.js → settings-dropdown-input-BjunGXJb.js} +1 -1
- package/build/assets/{settings-index-Cct3PJSP.js → settings-index-BjeuYFFa.js} +1 -1
- package/build/assets/{settings-input-DYs7t3oK.js → settings-input-bD6npsDm.js} +1 -1
- package/build/assets/{settings-list-classes-DvQr5jVS.js → settings-list-classes-Dp_OgzN0.js} +1 -1
- package/build/assets/{settings-modal-DwLybV_O.js → settings-modal-DfTcL-mV.js} +1 -1
- package/build/assets/settings-service.api-K4ncGlBv.js +1 -0
- package/build/assets/{settings-switch-C2N2xqEb.js → settings-switch-5cuxl8Dc.js} +1 -1
- package/build/assets/{shared-conversation-DnDwfLgg.js → shared-conversation-DWtBkJv7.js} +1 -1
- package/build/assets/{sidebar-layout-BtpUSN3j.js → sidebar-layout-DuzJMMw-.js} +1 -1
- package/build/assets/{sidebar-mobile-menu-toggle-B8k3qu84.js → sidebar-mobile-menu-toggle-CMmAYkh8.js} +1 -1
- package/build/assets/{sidebar-nav-link-CIB3NBRW.js → sidebar-nav-link-5AlTT5-j.js} +1 -1
- package/build/assets/{skill-card-pill-row-D2bUe2kB.js → skill-card-pill-row-eTIkE4nz.js} +1 -1
- package/build/assets/{skills-plugins-B68ay6Pu.js → skills-plugins-BJ040luO.js} +1 -1
- package/build/assets/{skills-settings-A6sUy0T7.js → skills-settings-BYn4-AQQ.js} +1 -1
- package/build/assets/{styled-tooltip-DsKsFjuq.js → styled-tooltip-CgX2oRaK.js} +1 -1
- package/build/assets/{task-list-tab-BAThr9Ex.js → task-list-tab-0k9PhuqO.js} +1 -1
- package/build/assets/{terminal-BmT0_gIH.js → terminal-BsGcSpRS.js} +1 -1
- package/build/assets/{toggle-switch-DBlOOzBr.js → toggle-switch-2j5wwR85.js} +1 -1
- package/build/assets/{typography-YGPBGzOG.js → typography-D8mHDeNG.js} +1 -1
- package/build/assets/{use-acp-credential-form-Bs6s0DTU.js → use-acp-credential-form-EtnbtYt0.js} +1 -1
- package/build/assets/{use-activate-agent-profile-CKtTToSx.js → use-activate-agent-profile-WaAZYz4E.js} +1 -1
- package/build/assets/{use-active-agent-profile-CWLPpOtK.js → use-active-agent-profile-CYY3vGar.js} +1 -1
- package/build/assets/{use-active-conversation-DI5S5M1w.js → use-active-conversation-D-r929nx.js} +1 -1
- package/build/assets/{use-agent-profiles-DPcaYLpm.js → use-agent-profiles-DEAMFp-W.js} +1 -1
- package/build/assets/{use-agent-state-Buh3bQDh.js → use-agent-state-DijdVAQ0.js} +1 -1
- package/build/assets/{use-backends-health-CxkvJy2N.js → use-backends-health-DkC7tKkC.js} +1 -1
- package/build/assets/{use-can-manage-org-profiles-DeX2Mfje.js → use-can-manage-org-profiles-BEo0peGn.js} +1 -1
- package/build/assets/{use-cloud-current-user-id-D_PQGfmT.js → use-cloud-current-user-id-Dm99wXp2.js} +1 -1
- package/build/assets/{use-config-ur0RbjCP.js → use-config-CHzNIHpv.js} +1 -1
- package/build/assets/{use-create-conversation-D9OWYcte.js → use-create-conversation-CTsvZ96U.js} +1 -1
- package/build/assets/{use-create-secret-CVFSlhlR.js → use-create-secret-D5O5qdWC.js} +1 -1
- package/build/assets/{use-event-store-YSOxy9FC.js → use-event-store-DjgU4wfO.js} +1 -1
- package/build/assets/{use-handle-plan-click-Dug8egQl.js → use-handle-plan-click-Mn_SNTCh.js} +1 -1
- package/build/assets/{use-llm-profiles-DjackuP1.js → use-llm-profiles-BMhPDlwQ.js} +1 -1
- package/build/assets/use-runtime-is-ready-CC-KrLKe.js +1 -0
- package/build/assets/{use-save-agent-profile-aKKLqDJM.js → use-save-agent-profile-CLMmbx_X.js} +1 -1
- package/build/assets/{use-save-settings-CChDyjOB.js → use-save-settings-D1HoCDbn.js} +1 -1
- package/build/assets/{use-settings-dX4fKUPF.js → use-settings-BReAMxCG.js} +1 -1
- package/build/assets/{use-settings-nav-items-D6pHnvxg.js → use-settings-nav-items-xe1YgLnE.js} +1 -1
- package/build/assets/{use-task-list-CmWGElgb.js → use-task-list-AVBLbbcR.js} +1 -1
- package/build/assets/{use-tracking-GtXCHmTv.js → use-tracking-2ZwQwpg_.js} +1 -1
- package/build/assets/{use-user-conversation-6uOXFP95.js → use-user-conversation-OAfenBRG.js} +1 -1
- package/build/assets/{useTranslation-Cgy4JA_g.js → useTranslation-CDRCKC9-.js} +1 -1
- package/build/assets/{utils-CE0lDWtb.js → utils-D3U3rlqr.js} +1 -1
- package/build/assets/{verification-settings-D_zsNB_c.js → verification-settings-DnXS6VWv.js} +1 -1
- package/build/index.html +4 -4
- package/build/locales/ar/openhands.json +24 -0
- package/build/locales/ca/openhands.json +24 -0
- package/build/locales/de/openhands.json +24 -0
- package/build/locales/en/openhands.json +24 -0
- package/build/locales/es/openhands.json +24 -0
- package/build/locales/fr/openhands.json +24 -0
- package/build/locales/it/openhands.json +24 -0
- package/build/locales/ja/openhands.json +24 -0
- package/build/locales/ko-KR/openhands.json +24 -0
- package/build/locales/no/openhands.json +24 -0
- package/build/locales/pt/openhands.json +24 -0
- package/build/locales/tr/openhands.json +24 -0
- package/build/locales/uk/openhands.json +24 -0
- package/build/locales/zh-CN/openhands.json +24 -0
- package/build/locales/zh-TW/openhands.json +24 -0
- package/config/defaults.json +4 -4
- package/dist/api/automation-service/automation-service.api.d.ts +2 -1
- package/dist/api/event-service/event-service.api.cjs +1 -1
- package/dist/api/event-service/event-service.api.cjs.map +1 -1
- package/dist/api/event-service/event-service.api.js +1 -1
- package/dist/api/event-service/event-service.api.js.map +1 -1
- package/dist/api/event-service/event-service.types.d.ts +6 -0
- package/dist/api/git-service/agent-server-git-service.api.cjs +1 -1
- package/dist/api/git-service/agent-server-git-service.api.cjs.map +1 -1
- package/dist/api/git-service/agent-server-git-service.api.js +2 -2
- package/dist/api/git-service/agent-server-git-service.api.js.map +1 -1
- package/dist/api/git-service/git-service.api.cjs +1 -1
- package/dist/api/git-service/git-service.api.cjs.map +1 -1
- package/dist/api/git-service/git-service.api.js +2 -2
- package/dist/api/git-service/git-service.api.js.map +1 -1
- package/dist/components/conversation-events/chat/event-thought-helpers.cjs +1 -1
- package/dist/components/conversation-events/chat/event-thought-helpers.js +1 -1
- package/dist/components/features/automations/automation-card.d.ts +2 -1
- package/dist/components/features/automations/automation-group.d.ts +2 -1
- package/dist/components/features/automations/automation-list-row.d.ts +2 -1
- package/dist/components/features/automations/build-automation-menu-items.d.ts +2 -1
- package/dist/components/features/automations/detail/detail-header.d.ts +3 -1
- package/dist/components/features/automations/import-automation-modal.d.ts +10 -0
- package/dist/components/features/conversation/conversation-name-context-menu.cjs +1 -1
- package/dist/components/features/conversation/conversation-name-context-menu.cjs.map +1 -1
- package/dist/components/features/conversation/conversation-name-context-menu.d.ts +2 -1
- package/dist/components/features/conversation/conversation-name-context-menu.js +74 -63
- package/dist/components/features/conversation/conversation-name-context-menu.js.map +1 -1
- package/dist/components/features/conversation/conversation-name.cjs +1 -1
- package/dist/components/features/conversation/conversation-name.cjs.map +1 -1
- package/dist/components/features/conversation/conversation-name.js +73 -61
- package/dist/components/features/conversation/conversation-name.js.map +1 -1
- package/dist/components/features/conversation/transcript-export-modal.cjs +2 -0
- package/dist/components/features/conversation/transcript-export-modal.cjs.map +1 -0
- package/dist/components/features/conversation/transcript-export-modal.d.ts +10 -0
- package/dist/components/features/conversation/transcript-export-modal.js +140 -0
- package/dist/components/features/conversation/transcript-export-modal.js.map +1 -0
- 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.js +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-context-menu.js.map +1 -1
- package/dist/components/features/home/shared/dropdown-item.cjs +1 -1
- package/dist/components/features/home/shared/dropdown-item.cjs.map +1 -1
- package/dist/components/features/home/shared/dropdown-item.d.ts +7 -1
- package/dist/components/features/home/shared/dropdown-item.js +2 -1
- package/dist/components/features/home/shared/dropdown-item.js.map +1 -1
- package/dist/components/features/home/shared/generic-dropdown-menu.cjs +1 -1
- package/dist/components/features/home/shared/generic-dropdown-menu.cjs.map +1 -1
- package/dist/components/features/home/shared/generic-dropdown-menu.d.ts +8 -1
- package/dist/components/features/home/shared/generic-dropdown-menu.js +18 -10
- package/dist/components/features/home/shared/generic-dropdown-menu.js.map +1 -1
- package/dist/components/features/home/workspace-dropdown/workspace-dropdown.d.ts +11 -2
- package/dist/config/defaults.cjs +1 -1
- package/dist/config/defaults.cjs.map +1 -1
- package/dist/config/defaults.js +4 -4
- package/dist/config/defaults.js.map +1 -1
- package/dist/hooks/query/use-automations.d.ts +2 -1
- package/dist/hooks/query/use-resolved-workspaces.cjs +1 -1
- package/dist/hooks/query/use-resolved-workspaces.cjs.map +1 -1
- package/dist/hooks/query/use-resolved-workspaces.d.ts +9 -1
- package/dist/hooks/query/use-resolved-workspaces.js +1 -0
- package/dist/hooks/query/use-resolved-workspaces.js.map +1 -1
- package/dist/hooks/query/use-workspace-file-content.cjs +1 -1
- package/dist/hooks/query/use-workspace-file-content.cjs.map +1 -1
- package/dist/hooks/query/use-workspace-file-content.js +61 -55
- package/dist/hooks/query/use-workspace-file-content.js.map +1 -1
- package/dist/hooks/query/use-workspace-files.cjs +1 -1
- package/dist/hooks/query/use-workspace-files.cjs.map +1 -1
- package/dist/hooks/query/use-workspace-files.d.ts +11 -4
- package/dist/hooks/query/use-workspace-files.js +39 -18
- package/dist/hooks/query/use-workspace-files.js.map +1 -1
- package/dist/hooks/use-auto-refresh-files-on-edit.cjs +1 -1
- package/dist/hooks/use-auto-refresh-files-on-edit.cjs.map +1 -1
- package/dist/hooks/use-auto-refresh-files-on-edit.d.ts +9 -0
- package/dist/hooks/use-auto-refresh-files-on-edit.js +15 -11
- package/dist/hooks/use-auto-refresh-files-on-edit.js.map +1 -1
- package/dist/hooks/use-tracking.cjs +1 -1
- package/dist/hooks/use-tracking.cjs.map +1 -1
- package/dist/hooks/use-tracking.d.ts +7 -0
- package/dist/hooks/use-tracking.js +9 -0
- package/dist/hooks/use-tracking.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 +24 -0
- package/dist/i18n/declaration.js +1 -1
- package/dist/i18n/declaration.js.map +1 -1
- package/dist/i18n/translation.cjs +1 -1
- package/dist/i18n/translation.cjs.map +1 -1
- package/dist/i18n/translation.js +408 -0
- package/dist/i18n/translation.js.map +1 -1
- package/dist/locales/ar/openhands.json +24 -0
- package/dist/locales/ca/openhands.json +24 -0
- package/dist/locales/de/openhands.json +24 -0
- package/dist/locales/en/openhands.json +24 -0
- package/dist/locales/es/openhands.json +24 -0
- package/dist/locales/fr/openhands.json +24 -0
- package/dist/locales/it/openhands.json +24 -0
- package/dist/locales/ja/openhands.json +24 -0
- package/dist/locales/ko-KR/openhands.json +24 -0
- package/dist/locales/no/openhands.json +24 -0
- package/dist/locales/pt/openhands.json +24 -0
- package/dist/locales/tr/openhands.json +24 -0
- package/dist/locales/uk/openhands.json +24 -0
- package/dist/locales/zh-CN/openhands.json +24 -0
- package/dist/locales/zh-TW/openhands.json +24 -0
- package/dist/package.cjs +1 -1
- package/dist/package.cjs.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/dist/routes/files-tab.cjs +1 -1
- package/dist/routes/files-tab.js +10 -10
- package/dist/types/automation.d.ts +8 -0
- package/dist/utils/custom-toast-handlers.cjs.map +1 -1
- package/dist/utils/custom-toast-handlers.d.ts +1 -0
- package/dist/utils/custom-toast-handlers.js.map +1 -1
- package/dist/utils/mcp-server-name.cjs +1 -1
- package/dist/utils/mcp-server-name.cjs.map +1 -1
- package/dist/utils/mcp-server-name.js +2 -2
- package/dist/utils/mcp-server-name.js.map +1 -1
- package/dist/utils/transcript-export/index.cjs +72 -0
- package/dist/utils/transcript-export/index.cjs.map +1 -0
- package/dist/utils/transcript-export/index.d.ts +10 -0
- package/dist/utils/transcript-export/index.js +302 -0
- package/dist/utils/transcript-export/index.js.map +1 -0
- package/dist/utils/transcript-export/load-complete-events.cjs +2 -0
- package/dist/utils/transcript-export/load-complete-events.cjs.map +1 -0
- package/dist/utils/transcript-export/load-complete-events.d.ts +22 -0
- package/dist/utils/transcript-export/load-complete-events.js +39 -0
- package/dist/utils/transcript-export/load-complete-events.js.map +1 -0
- package/package.json +1 -1
- package/scripts/check-sdk-version-sync.mjs +6 -6
- package/scripts/dev-safe.mjs +2 -2
- package/build/assets/active-backend-context-u2tdYRgH.js +0 -1
- package/build/assets/agent-settings-FGL3Yvkp.js +0 -1
- package/build/assets/automation-detail-DwYGzLkp.js +0 -2
- package/build/assets/automations-list-CuAfJmMW.js +0 -1
- package/build/assets/conversation-DnVC6vRV.js +0 -1
- package/build/assets/conversation-panel-ZEz-z3az.js +0 -1
- package/build/assets/conversation-xkBS3AHC.js +0 -19
- package/build/assets/declaration-B9kpCaYl.js +0 -1
- package/build/assets/edit-automation-modal-B6U96gVV.js +0 -1
- package/build/assets/files-tab-61Vaihc6.js +0 -1
- package/build/assets/home-5gOtUe-n.js +0 -1
- package/build/assets/lesson-plan-BtrZUCuj.js +0 -4
- package/build/assets/llm-settings-DsInf6yD.js +0 -1
- package/build/assets/manifest-a5bb7568.js +0 -1
- package/build/assets/onboarding-D32kiGME.js +0 -1
- package/build/assets/providers-vX9jk9Vy.js +0 -1
- package/build/assets/root-CxWZUXTQ.css +0 -1
- package/build/assets/root-layout-CoWw4kpz.js +0 -2
- package/build/assets/settings-service.api-DldkFI1x.js +0 -1
- package/build/assets/use-runtime-is-ready-Diiq65Q-.js +0 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { getActiveBackend as e } from "../../api/backend-registry/active-store.js";
|
|
2
2
|
import { useQuery as t } from "../../node_modules/@tanstack/react-query/build/modern/useQuery.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import i from "
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
3
|
+
import { readCloudConversationFile as n } from "../../api/cloud/conversation-service.api.js";
|
|
4
|
+
import { useActiveConversation as r } from "./use-active-conversation.js";
|
|
5
|
+
import { useRuntimeIsReady as i } from "../use-runtime-is-ready.js";
|
|
6
|
+
import { getGitPath as a } from "../../utils/get-git-path.js";
|
|
7
|
+
import { joinWorkspaceUrl as o, useWorkspaceSession as s } from "./use-workspace-session.js";
|
|
8
|
+
import { useWorkspaceMutationCounter as c } from "../../stores/use-workspace-mutation-counter.js";
|
|
8
9
|
//#region src/hooks/query/use-workspace-file-content.ts
|
|
9
|
-
var
|
|
10
|
+
var l = new Set([
|
|
10
11
|
"png",
|
|
11
12
|
"jpg",
|
|
12
13
|
"jpeg",
|
|
@@ -16,13 +17,13 @@ var c = new Set([
|
|
|
16
17
|
"ico",
|
|
17
18
|
"svg",
|
|
18
19
|
"avif"
|
|
19
|
-
]),
|
|
20
|
-
function
|
|
20
|
+
]), u = new Set(["pdf"]);
|
|
21
|
+
function d(e) {
|
|
21
22
|
let t = e.lastIndexOf(".");
|
|
22
23
|
return t === -1 ? "" : e.slice(t + 1).toLowerCase();
|
|
23
24
|
}
|
|
24
|
-
function
|
|
25
|
-
switch (
|
|
25
|
+
function f(e) {
|
|
26
|
+
switch (d(e)) {
|
|
26
27
|
case "html":
|
|
27
28
|
case "htm": return "text/html";
|
|
28
29
|
case "css": return "text/css";
|
|
@@ -45,84 +46,89 @@ function d(e) {
|
|
|
45
46
|
default: return "text/plain";
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
|
-
function f(e) {
|
|
49
|
-
let t = u(e);
|
|
50
|
-
return c.has(t) ? "image" : l.has(t) ? "pdf" : "text";
|
|
51
|
-
}
|
|
52
49
|
function p(e) {
|
|
50
|
+
let t = d(e);
|
|
51
|
+
return l.has(t) ? "image" : u.has(t) ? "pdf" : "text";
|
|
52
|
+
}
|
|
53
|
+
function m(e) {
|
|
53
54
|
let t = new Uint8Array(e, 0, Math.min(e.byteLength, 8e3));
|
|
54
55
|
for (let e = 0; e < t.length; e += 1) if (t[e] === 0) return !0;
|
|
55
56
|
return !1;
|
|
56
57
|
}
|
|
57
|
-
function
|
|
58
|
+
function h(e) {
|
|
58
59
|
let t = new Uint8Array(e), n = 32768, r = "";
|
|
59
60
|
for (let e = 0; e < t.length; e += n) r += String.fromCharCode.apply(null, t.subarray(e, e + n));
|
|
60
61
|
return btoa(r);
|
|
61
62
|
}
|
|
62
|
-
function
|
|
63
|
-
let { data:
|
|
63
|
+
function g(l) {
|
|
64
|
+
let { data: u } = r(), d = i(), { data: g } = s(), _ = c((e) => e.count), v = u?.id, y = u?.conversation_url, b = u?.session_api_key, x = u?.selected_repository, S = u?.workspace?.working_dir?.trim(), C = g?.baseUrl, w = e().backend.kind === "cloud", T = a(x, S), E = T.startsWith("/") ? T : `/${T}`, D = l ? `${E}/${l}` : null;
|
|
64
65
|
return t({
|
|
65
66
|
queryKey: [
|
|
66
67
|
"workspace-file-content",
|
|
67
|
-
_,
|
|
68
68
|
v,
|
|
69
69
|
y,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
b,
|
|
71
|
+
w ? "cloud" : C,
|
|
72
|
+
l,
|
|
73
|
+
D,
|
|
74
|
+
_
|
|
73
75
|
],
|
|
74
76
|
queryFn: async () => {
|
|
75
|
-
if (!
|
|
76
|
-
let e =
|
|
77
|
-
if (
|
|
78
|
-
let
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
77
|
+
if (!l) throw Error("No path");
|
|
78
|
+
let e = p(l), t = f(l);
|
|
79
|
+
if (w) {
|
|
80
|
+
let r = await n(v, D);
|
|
81
|
+
if (e === "text") {
|
|
82
|
+
let e = new TextEncoder().encode(r);
|
|
83
|
+
return m(e.buffer) ? {
|
|
84
|
+
path: l,
|
|
85
|
+
kind: "binary",
|
|
86
|
+
text: null,
|
|
87
|
+
staticUrl: `data:application/octet-stream;base64,${h(e.buffer)}`,
|
|
88
|
+
mimeType: "application/octet-stream"
|
|
89
|
+
} : {
|
|
90
|
+
path: l,
|
|
91
|
+
kind: "text",
|
|
92
|
+
text: r,
|
|
93
|
+
staticUrl: `data:${t};charset=utf-8;base64,${h(e.buffer)}`,
|
|
94
|
+
mimeType: t
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
path: l,
|
|
93
99
|
kind: e,
|
|
94
100
|
text: null,
|
|
95
|
-
staticUrl: `data:${t};base64,${
|
|
101
|
+
staticUrl: `data:${t};base64,${h(new TextEncoder().encode(r).buffer)}`,
|
|
96
102
|
mimeType: t
|
|
97
103
|
};
|
|
98
104
|
}
|
|
99
|
-
if (!
|
|
100
|
-
let
|
|
105
|
+
if (!C) throw Error("No workspace session");
|
|
106
|
+
let r = o(C, l);
|
|
101
107
|
if (e !== "text") return {
|
|
102
|
-
path:
|
|
108
|
+
path: l,
|
|
103
109
|
kind: e,
|
|
104
110
|
text: null,
|
|
105
|
-
staticUrl:
|
|
111
|
+
staticUrl: r,
|
|
106
112
|
mimeType: t
|
|
107
113
|
};
|
|
108
|
-
let
|
|
109
|
-
if (!
|
|
110
|
-
let
|
|
111
|
-
return
|
|
112
|
-
path:
|
|
114
|
+
let i = await fetch(r, { credentials: "include" });
|
|
115
|
+
if (!i.ok) throw Error(`Failed to read ${l}: ${i.status}`);
|
|
116
|
+
let a = await i.arrayBuffer();
|
|
117
|
+
return m(a) ? {
|
|
118
|
+
path: l,
|
|
113
119
|
kind: "binary",
|
|
114
120
|
text: null,
|
|
115
|
-
staticUrl:
|
|
121
|
+
staticUrl: r,
|
|
116
122
|
mimeType: "application/octet-stream"
|
|
117
123
|
} : {
|
|
118
|
-
path:
|
|
124
|
+
path: l,
|
|
119
125
|
kind: "text",
|
|
120
|
-
text: new TextDecoder("utf-8", { fatal: !1 }).decode(
|
|
121
|
-
staticUrl:
|
|
126
|
+
text: new TextDecoder("utf-8", { fatal: !1 }).decode(a),
|
|
127
|
+
staticUrl: r,
|
|
122
128
|
mimeType: t
|
|
123
129
|
};
|
|
124
130
|
},
|
|
125
|
-
enabled:
|
|
131
|
+
enabled: d && !!v && !!l && (w || !!C),
|
|
126
132
|
retry: !1,
|
|
127
133
|
staleTime: 1e3 * 5,
|
|
128
134
|
gcTime: 1e3 * 60,
|
|
@@ -130,6 +136,6 @@ function h(c) {
|
|
|
130
136
|
});
|
|
131
137
|
}
|
|
132
138
|
//#endregion
|
|
133
|
-
export {
|
|
139
|
+
export { g as useWorkspaceFileContent };
|
|
134
140
|
|
|
135
141
|
//# sourceMappingURL=use-workspace-file-content.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-workspace-file-content.js","names":[],"sources":["../../../src/hooks/query/use-workspace-file-content.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\n\nimport { getActiveBackend } from \"#/api/backend-registry/active-store\";\nimport AgentServerRuntimeService from \"#/api/runtime-service/agent-server-runtime-service\";\nimport { useActiveConversation } from \"#/hooks/query/use-active-conversation\";\nimport { useRuntimeIsReady } from \"#/hooks/use-runtime-is-ready\";\nimport {\n joinWorkspaceUrl,\n useWorkspaceSession,\n} from \"#/hooks/query/use-workspace-session\";\nimport { useWorkspaceMutationCounter } from \"#/stores/use-workspace-mutation-counter\";\n\n// Magic-number sniff for common binary formats we can render via iframe.\nconst IMAGE_EXTENSIONS = new Set([\n \"png\",\n \"jpg\",\n \"jpeg\",\n \"gif\",\n \"webp\",\n \"bmp\",\n \"ico\",\n \"svg\",\n \"avif\",\n]);\n\nconst PDF_EXTENSIONS = new Set([\"pdf\"]);\n\nexport type WorkspaceFileKind = \"text\" | \"image\" | \"pdf\" | \"binary\";\n\nexport interface WorkspaceFileContent {\n path: string;\n kind: WorkspaceFileKind;\n /** Decoded text contents — only populated when kind === \"text\". */\n text: string | null;\n /**\n * URL pointing at the file on the agent server's static workspace\n * fileserver (the `/api/conversations/{id}/workspace/...` route minted\n * by `RemoteWorkspace.startWorkspaceSession`). Suitable to use as an\n * `<iframe src>` or `<img src>` — the workspace-session cookie\n * authenticates the browser request, and relative asset references\n * inside an HTML preview resolve naturally against this URL.\n */\n staticUrl: string;\n /** MIME type guessed from the file extension. */\n mimeType: string;\n}\n\nfunction getExtension(path: string): string {\n const idx = path.lastIndexOf(\".\");\n if (idx === -1) return \"\";\n return path.slice(idx + 1).toLowerCase();\n}\n\nfunction guessMimeType(path: string): string {\n const ext = getExtension(path);\n switch (ext) {\n case \"html\":\n case \"htm\":\n return \"text/html\";\n case \"css\":\n return \"text/css\";\n case \"js\":\n case \"mjs\":\n case \"cjs\":\n return \"text/javascript\";\n case \"json\":\n return \"application/json\";\n case \"md\":\n case \"markdown\":\n return \"text/markdown\";\n case \"svg\":\n return \"image/svg+xml\";\n case \"png\":\n return \"image/png\";\n case \"jpg\":\n case \"jpeg\":\n return \"image/jpeg\";\n case \"gif\":\n return \"image/gif\";\n case \"webp\":\n return \"image/webp\";\n case \"bmp\":\n return \"image/bmp\";\n case \"ico\":\n return \"image/x-icon\";\n case \"avif\":\n return \"image/avif\";\n case \"pdf\":\n return \"application/pdf\";\n default:\n return \"text/plain\";\n }\n}\n\nfunction classifyKind(path: string): WorkspaceFileKind {\n const ext = getExtension(path);\n if (IMAGE_EXTENSIONS.has(ext)) return \"image\";\n if (PDF_EXTENSIONS.has(ext)) return \"pdf\";\n // Everything else is treated as text and decoded; if decoding produces\n // null bytes we fall back to \"binary\" downstream.\n return \"text\";\n}\n\nfunction isLikelyBinary(buffer: ArrayBuffer): boolean {\n // Same heuristic git uses: presence of a NUL byte in the first ~8KB.\n const view = new Uint8Array(buffer, 0, Math.min(buffer.byteLength, 8000));\n for (let i = 0; i < view.length; i += 1) {\n if (view[i] === 0) return true;\n }\n return false;\n}\n\nfunction arrayBufferToBase64(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n // Chunk to stay under the call-stack limit of `String.fromCharCode(...arr)`\n // for larger files (>~100KB) while avoiding per-byte allocation.\n const CHUNK = 0x8000;\n let binary = \"\";\n for (let i = 0; i < bytes.length; i += CHUNK) {\n binary += String.fromCharCode.apply(\n null,\n bytes.subarray(i, i + CHUNK) as unknown as number[],\n );\n }\n return btoa(binary);\n}\n\n/**\n * Reads a single file out of the active conversation's workspace via the\n * agent server's static workspace fileserver and classifies it as\n * text/image/pdf/binary so the UI can pick a renderer.\n *\n * Image and PDF kinds are rendered directly from `staticUrl` (no fetch\n * here). Text/binary classification still requires reading the body so\n * we can run a NUL-byte sniff and decode UTF-8 for the plain/markdown\n * renderers.\n *\n * Pass a falsy `relativePath` to disable the query (e.g. when no file is\n * selected yet).\n */\nexport function useWorkspaceFileContent(relativePath: string | null) {\n const { data: conversation } = useActiveConversation();\n const runtimeIsReady = useRuntimeIsReady();\n const { data: workspaceSession } = useWorkspaceSession();\n // Bump on every agent-side file mutation so the query refetches the\n // currently-selected file's body even when the *path* hasn't changed.\n // The iframe / <img> cache-busting for the rich preview is handled at\n // the consumer (FileContentViewer / files-tab) by appending the same\n // counter to the staticUrl, so a single tick refreshes both the\n // decoded text and the iframe-rendered HTML's sibling assets.\n const workspaceMutationCount = useWorkspaceMutationCounter(\n (state) => state.count,\n );\n\n const conversationId = conversation?.id;\n const conversationUrl = conversation?.conversation_url;\n const sessionApiKey = conversation?.session_api_key;\n const baseUrl = workspaceSession?.baseUrl;\n const isCloud = getActiveBackend().backend.kind === \"cloud\";\n\n return useQuery<WorkspaceFileContent>({\n queryKey: [\n \"workspace-file-content\",\n conversationId,\n conversationUrl,\n sessionApiKey,\n isCloud ? \"cloud\" : baseUrl,\n relativePath,\n workspaceMutationCount,\n ],\n queryFn: async () => {\n if (!relativePath) throw new Error(\"No path\");\n\n const kind = classifyKind(relativePath);\n const mimeType = guessMimeType(relativePath);\n\n if (isCloud) {\n // Cloud: no static fileserver cookie path is reachable from the\n // browser. Fetch bytes server-side through callCloudProxy and\n // hand the consumer a self-contained `data:` URI for iframe /\n // <img> rendering.\n const buffer = await AgentServerRuntimeService.downloadFile(\n conversationUrl,\n sessionApiKey,\n relativePath,\n );\n if (kind === \"text\") {\n if (isLikelyBinary(buffer)) {\n return {\n path: relativePath,\n kind: \"binary\",\n text: null,\n staticUrl: `data:application/octet-stream;base64,${arrayBufferToBase64(buffer)}`,\n mimeType: \"application/octet-stream\",\n };\n }\n const text = new TextDecoder(\"utf-8\", { fatal: false }).decode(\n buffer,\n );\n return {\n path: relativePath,\n kind: \"text\",\n text,\n staticUrl: `data:${mimeType};charset=utf-8;base64,${arrayBufferToBase64(buffer)}`,\n mimeType,\n };\n }\n return {\n path: relativePath,\n kind,\n text: null,\n staticUrl: `data:${mimeType};base64,${arrayBufferToBase64(buffer)}`,\n mimeType,\n };\n }\n\n // Local: rely on the workspace-session cookie minted by\n // useWorkspaceSession to authenticate the same-origin static\n // fileserver fetch.\n if (!baseUrl) throw new Error(\"No workspace session\");\n\n const staticUrl = joinWorkspaceUrl(baseUrl, relativePath);\n\n // Image / PDF: don't fetch the bytes — the consumer renders them\n // directly via `staticUrl` in an iframe or <img>. The browser\n // will attach the `oh_workspace_session_key` cookie minted by\n // `useWorkspaceSession` so the request authenticates without us\n // having to set any headers (which a top-level <iframe src> can't\n // do anyway).\n if (kind !== \"text\") {\n return {\n path: relativePath,\n kind,\n text: null,\n staticUrl,\n mimeType,\n };\n }\n\n // For our own fetch we also rely on the workspace-session cookie\n // (it travels because we opt in to credentialed requests). This\n // matches the auth path the iframe / <img> uses, and avoids a CORS\n // preflight for a custom header.\n const response = await fetch(staticUrl, {\n credentials: \"include\",\n });\n if (!response.ok) {\n throw new Error(`Failed to read ${relativePath}: ${response.status}`);\n }\n\n const buffer = await response.arrayBuffer();\n if (isLikelyBinary(buffer)) {\n return {\n path: relativePath,\n kind: \"binary\",\n text: null,\n staticUrl,\n mimeType: \"application/octet-stream\",\n };\n }\n\n const text = new TextDecoder(\"utf-8\", { fatal: false }).decode(buffer);\n return {\n path: relativePath,\n kind: \"text\",\n text,\n staticUrl,\n mimeType,\n };\n },\n enabled:\n runtimeIsReady &&\n !!conversationId &&\n !!relativePath &&\n (isCloud || !!baseUrl),\n retry: false,\n staleTime: 1000 * 5,\n gcTime: 1000 * 60,\n meta: { disableToast: true },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-workspace-file-content.js","names":[],"sources":["../../../src/hooks/query/use-workspace-file-content.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\n\nimport { readCloudConversationFile } from \"#/api/cloud/conversation-service.api\";\nimport { getActiveBackend } from \"#/api/backend-registry/active-store\";\nimport { getGitPath } from \"#/utils/get-git-path\";\nimport { useActiveConversation } from \"#/hooks/query/use-active-conversation\";\nimport { useRuntimeIsReady } from \"#/hooks/use-runtime-is-ready\";\nimport {\n joinWorkspaceUrl,\n useWorkspaceSession,\n} from \"#/hooks/query/use-workspace-session\";\nimport { useWorkspaceMutationCounter } from \"#/stores/use-workspace-mutation-counter\";\n\n// Magic-number sniff for common binary formats we can render via iframe.\nconst IMAGE_EXTENSIONS = new Set([\n \"png\",\n \"jpg\",\n \"jpeg\",\n \"gif\",\n \"webp\",\n \"bmp\",\n \"ico\",\n \"svg\",\n \"avif\",\n]);\n\nconst PDF_EXTENSIONS = new Set([\"pdf\"]);\n\nexport type WorkspaceFileKind = \"text\" | \"image\" | \"pdf\" | \"binary\";\n\nexport interface WorkspaceFileContent {\n path: string;\n kind: WorkspaceFileKind;\n /** Decoded text contents — only populated when kind === \"text\". */\n text: string | null;\n /**\n * URL pointing at the file on the agent server's static workspace\n * fileserver (the `/api/conversations/{id}/workspace/...` route minted\n * by `RemoteWorkspace.startWorkspaceSession`). Suitable to use as an\n * `<iframe src>` or `<img src>` — the workspace-session cookie\n * authenticates the browser request, and relative asset references\n * inside an HTML preview resolve naturally against this URL.\n */\n staticUrl: string;\n /** MIME type guessed from the file extension. */\n mimeType: string;\n}\n\nfunction getExtension(path: string): string {\n const idx = path.lastIndexOf(\".\");\n if (idx === -1) return \"\";\n return path.slice(idx + 1).toLowerCase();\n}\n\nfunction guessMimeType(path: string): string {\n const ext = getExtension(path);\n switch (ext) {\n case \"html\":\n case \"htm\":\n return \"text/html\";\n case \"css\":\n return \"text/css\";\n case \"js\":\n case \"mjs\":\n case \"cjs\":\n return \"text/javascript\";\n case \"json\":\n return \"application/json\";\n case \"md\":\n case \"markdown\":\n return \"text/markdown\";\n case \"svg\":\n return \"image/svg+xml\";\n case \"png\":\n return \"image/png\";\n case \"jpg\":\n case \"jpeg\":\n return \"image/jpeg\";\n case \"gif\":\n return \"image/gif\";\n case \"webp\":\n return \"image/webp\";\n case \"bmp\":\n return \"image/bmp\";\n case \"ico\":\n return \"image/x-icon\";\n case \"avif\":\n return \"image/avif\";\n case \"pdf\":\n return \"application/pdf\";\n default:\n return \"text/plain\";\n }\n}\n\nfunction classifyKind(path: string): WorkspaceFileKind {\n const ext = getExtension(path);\n if (IMAGE_EXTENSIONS.has(ext)) return \"image\";\n if (PDF_EXTENSIONS.has(ext)) return \"pdf\";\n // Everything else is treated as text and decoded; if decoding produces\n // null bytes we fall back to \"binary\" downstream.\n return \"text\";\n}\n\nfunction isLikelyBinary(buffer: ArrayBuffer): boolean {\n // Same heuristic git uses: presence of a NUL byte in the first ~8KB.\n const view = new Uint8Array(buffer, 0, Math.min(buffer.byteLength, 8000));\n for (let i = 0; i < view.length; i += 1) {\n if (view[i] === 0) return true;\n }\n return false;\n}\n\nfunction arrayBufferToBase64(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n // Chunk to stay under the call-stack limit of `String.fromCharCode(...arr)`\n // for larger files (>~100KB) while avoiding per-byte allocation.\n const CHUNK = 0x8000;\n let binary = \"\";\n for (let i = 0; i < bytes.length; i += CHUNK) {\n binary += String.fromCharCode.apply(\n null,\n bytes.subarray(i, i + CHUNK) as unknown as number[],\n );\n }\n return btoa(binary);\n}\n\n/**\n * Reads a single file out of the active conversation's workspace via the\n * agent server's static workspace fileserver and classifies it as\n * text/image/pdf/binary so the UI can pick a renderer.\n *\n * Image and PDF kinds are rendered directly from `staticUrl` (no fetch\n * here). Text/binary classification still requires reading the body so\n * we can run a NUL-byte sniff and decode UTF-8 for the plain/markdown\n * renderers.\n *\n * Pass a falsy `relativePath` to disable the query (e.g. when no file is\n * selected yet).\n */\nexport function useWorkspaceFileContent(relativePath: string | null) {\n const { data: conversation } = useActiveConversation();\n const runtimeIsReady = useRuntimeIsReady();\n const { data: workspaceSession } = useWorkspaceSession();\n // Bump on every agent-side file mutation so the query refetches the\n // currently-selected file's body even when the *path* hasn't changed.\n // The iframe / <img> cache-busting for the rich preview is handled at\n // the consumer (FileContentViewer / files-tab) by appending the same\n // counter to the staticUrl, so a single tick refreshes both the\n // decoded text and the iframe-rendered HTML's sibling assets.\n const workspaceMutationCount = useWorkspaceMutationCounter(\n (state) => state.count,\n );\n\n const conversationId = conversation?.id;\n const conversationUrl = conversation?.conversation_url;\n const sessionApiKey = conversation?.session_api_key;\n const selectedRepository = conversation?.selected_repository;\n const workingDir = conversation?.workspace?.working_dir?.trim();\n const baseUrl = workspaceSession?.baseUrl;\n const isCloud = getActiveBackend().backend.kind === \"cloud\";\n\n // The cloud `/file` endpoint downloads via the runtime's\n // `/api/file/download`, which rejects relative paths (400 → the cloud API\n // swallows it and returns \"\"). Anchor the file against the working dir the\n // same way the diff view builds its git-diff path (see use-unified-git-diff),\n // then force a leading slash since `getGitPath`'s default is relative.\n const gitPath = getGitPath(selectedRepository, workingDir);\n const workspaceRoot = gitPath.startsWith(\"/\") ? gitPath : `/${gitPath}`;\n const absoluteFilePath = relativePath\n ? `${workspaceRoot}/${relativePath}`\n : null;\n\n return useQuery<WorkspaceFileContent>({\n queryKey: [\n \"workspace-file-content\",\n conversationId,\n conversationUrl,\n sessionApiKey,\n isCloud ? \"cloud\" : baseUrl,\n relativePath,\n absoluteFilePath,\n workspaceMutationCount,\n ],\n queryFn: async () => {\n if (!relativePath) throw new Error(\"No path\");\n\n const kind = classifyKind(relativePath);\n const mimeType = guessMimeType(relativePath);\n\n if (isCloud) {\n // Cloud: fetch through the cloud API's first-class runtime proxy\n // (GET /api/v1/app-conversations/{id}/file), which avoids the\n // removed /api/cloud-proxy hop. The endpoint returns file content as\n // a string; binary files are detected via NUL-byte sniff on the\n // decoded result and served as base64 data URIs.\n const content = await readCloudConversationFile(\n conversationId!,\n absoluteFilePath!,\n );\n\n if (kind === \"text\") {\n // NUL-byte sniff on the decoded text to catch binary files that\n // the cloud endpoint decoded as UTF-8 (fallible but sufficient).\n const buf = new TextEncoder().encode(content);\n if (isLikelyBinary(buf.buffer)) {\n return {\n path: relativePath,\n kind: \"binary\",\n text: null,\n staticUrl: `data:application/octet-stream;base64,${arrayBufferToBase64(buf.buffer)}`,\n mimeType: \"application/octet-stream\",\n };\n }\n return {\n path: relativePath,\n kind: \"text\",\n text: content,\n staticUrl: `data:${mimeType};charset=utf-8;base64,${arrayBufferToBase64(buf.buffer)}`,\n mimeType,\n };\n }\n // Image / PDF via cloud API: the endpoint returns text, so binary\n // bytes are decoded as UTF-8 server-side and can't round-trip\n // faithfully. Best-effort base64 of the returned string — a proper\n // binary path needs a cloud download endpoint (the old byte-accurate\n // downloadFile route went through the removed /api/cloud-proxy).\n const buf = new TextEncoder().encode(content);\n return {\n path: relativePath,\n kind,\n text: null,\n staticUrl: `data:${mimeType};base64,${arrayBufferToBase64(buf.buffer)}`,\n mimeType,\n };\n }\n\n // Local: rely on the workspace-session cookie minted by\n // useWorkspaceSession to authenticate the same-origin static\n // fileserver fetch.\n if (!baseUrl) throw new Error(\"No workspace session\");\n\n const staticUrl = joinWorkspaceUrl(baseUrl, relativePath);\n\n // Image / PDF: don't fetch the bytes — the consumer renders them\n // directly via `staticUrl` in an iframe or <img>. The browser\n // will attach the `oh_workspace_session_key` cookie minted by\n // `useWorkspaceSession` so the request authenticates without us\n // having to set any headers (which a top-level <iframe src> can't\n // do anyway).\n if (kind !== \"text\") {\n return {\n path: relativePath,\n kind,\n text: null,\n staticUrl,\n mimeType,\n };\n }\n\n // For our own fetch we also rely on the workspace-session cookie\n // (it travels because we opt in to credentialed requests). This\n // matches the auth path the iframe / <img> uses, and avoids a CORS\n // preflight for a custom header.\n const response = await fetch(staticUrl, {\n credentials: \"include\",\n });\n if (!response.ok) {\n throw new Error(`Failed to read ${relativePath}: ${response.status}`);\n }\n\n const buffer = await response.arrayBuffer();\n if (isLikelyBinary(buffer)) {\n return {\n path: relativePath,\n kind: \"binary\",\n text: null,\n staticUrl,\n mimeType: \"application/octet-stream\",\n };\n }\n\n const text = new TextDecoder(\"utf-8\", { fatal: false }).decode(buffer);\n return {\n path: relativePath,\n kind: \"text\",\n text,\n staticUrl,\n mimeType,\n };\n },\n enabled:\n runtimeIsReady &&\n !!conversationId &&\n !!relativePath &&\n (isCloud || !!baseUrl),\n retry: false,\n staleTime: 1000 * 5,\n gcTime: 1000 * 60,\n meta: { disableToast: true },\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require(`../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../node_modules/@tanstack/react-query/build/modern/useQuery.cjs`),t=require(`./use-active-conversation.cjs`),
|
|
1
|
+
require(`../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../node_modules/@tanstack/react-query/build/modern/useQuery.cjs`),t=require(`../../contexts/active-backend-context.cjs`),n=require(`./use-active-conversation.cjs`),r=require(`../use-runtime-is-ready.cjs`),i=require(`../../api/runtime-service/agent-server-runtime-service.cjs`),a=require(`./use-unified-get-git-changes.cjs`);let o=require(`react`);var s=2e3,c=[`.git`,`node_modules`,`.venv`,`venv`,`__pycache__`,`dist`,`build`,`.next`,`.cache`,`.pytest_cache`,`.mypy_cache`,`.turbo`,`.parcel-cache`,`target`];function l(){return`find . \\( ${c.map(e=>`-name '${e}' -prune`).join(` -o `)} \\) -o -type f -print 2>/dev/null | sort | head -n ${s}`}function u(e){return e.startsWith(`./`)?e.slice(2):e}function d(t){let{data:a}=n.useActiveConversation(),o=r.useRuntimeIsReady(),c=a?.id,d=a?.conversation_url,f=a?.session_api_key,p=a?.workspace?.working_dir?.trim(),m=e.useQuery({queryKey:[`workspace-files`,c,d,f,p],queryFn:async()=>{let e=await i.default.executeCommand(d,f,l(),p,30);if(e.exit_code!==0)throw Error(e.stderr?.trim()||`Failed to list workspace files`);let t=e.stdout.split(/\r?\n/).map(e=>e.trim()).filter(Boolean).map(u);return Array.from(new Set(t)).slice(0,s)},enabled:t&&o&&!!c&&!!p,retry:!1,staleTime:1e3*30,gcTime:1e3*60*5,meta:{disableToast:!0}});return{data:m.data,isLoading:m.isLoading}}function f(e){let t=a.useUnifiedGetGitChanges(),n=(0,o.useMemo)(()=>{if(!e)return;let n=t.data.filter(e=>e.status!==`D`).map(e=>e.path);return Array.from(new Set(n)).slice(0,s)},[e,t.data]);return{data:e?n:void 0,isLoading:e?t.isLoading:!1}}function p(){let{backend:e}=t.useActiveBackend(),n=e.kind===`cloud`,r=d(!n),i=f(n);return n?i:r}exports.useWorkspaceFiles=p;
|
|
2
2
|
//# sourceMappingURL=use-workspace-files.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-workspace-files.cjs","names":[],"sources":["../../../src/hooks/query/use-workspace-files.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\n\nimport AgentServerRuntimeService from \"#/api/runtime-service/agent-server-runtime-service\";\nimport { useActiveConversation } from \"#/hooks/query/use-active-conversation\";\nimport { useRuntimeIsReady } from \"#/hooks/use-runtime-is-ready\";\n\n// Cap the number of files we render so a giant repo doesn't freeze the UI.\nconst MAX_FILES = 2000;\n\n// Directory names that we never want to descend into when listing files.\nconst EXCLUDED_DIRS = [\n \".git\",\n \"node_modules\",\n \".venv\",\n \"venv\",\n \"__pycache__\",\n \"dist\",\n \"build\",\n \".next\",\n \".cache\",\n \".pytest_cache\",\n \".mypy_cache\",\n \".turbo\",\n \".parcel-cache\",\n \"target\",\n];\n\n// Build a `find` invocation that lists files relative to the workspace root.\nfunction buildListCommand(): string {\n const pruneExpr = EXCLUDED_DIRS.map((dir) => `-name '${dir}' -prune`).join(\n \" -o \",\n );\n return `find . \\\\( ${pruneExpr} \\\\) -o -type f -print 2>/dev/null | sort | head -n ${MAX_FILES}`;\n}\n\nfunction normalizePath(path: string): string {\n // Strip a leading \"./\" so paths render cleanly in the UI.\n return path.startsWith(\"./\") ? path.slice(2) : path;\n}\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"use-workspace-files.cjs","names":[],"sources":["../../../src/hooks/query/use-workspace-files.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useQuery } from \"@tanstack/react-query\";\n\nimport AgentServerRuntimeService from \"#/api/runtime-service/agent-server-runtime-service\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport { useActiveConversation } from \"#/hooks/query/use-active-conversation\";\nimport { useRuntimeIsReady } from \"#/hooks/use-runtime-is-ready\";\nimport { useUnifiedGetGitChanges } from \"#/hooks/query/use-unified-get-git-changes\";\n\n// Cap the number of files we render so a giant repo doesn't freeze the UI.\nconst MAX_FILES = 2000;\n\nexport interface WorkspaceFilesResult {\n data: string[] | undefined;\n isLoading: boolean;\n}\n\n// Directory names that we never want to descend into when listing files.\nconst EXCLUDED_DIRS = [\n \".git\",\n \"node_modules\",\n \".venv\",\n \"venv\",\n \"__pycache__\",\n \"dist\",\n \"build\",\n \".next\",\n \".cache\",\n \".pytest_cache\",\n \".mypy_cache\",\n \".turbo\",\n \".parcel-cache\",\n \"target\",\n];\n\n// Build a `find` invocation that lists files relative to the workspace root.\nfunction buildListCommand(): string {\n const pruneExpr = EXCLUDED_DIRS.map((dir) => `-name '${dir}' -prune`).join(\n \" -o \",\n );\n return `find . \\\\( ${pruneExpr} \\\\) -o -type f -print 2>/dev/null | sort | head -n ${MAX_FILES}`;\n}\n\nfunction normalizePath(path: string): string {\n // Strip a leading \"./\" so paths render cleanly in the UI.\n return path.startsWith(\"./\") ? path.slice(2) : path;\n}\n\n/**\n * Local-backend listing: enumerate every regular file beneath the active\n * conversation's working directory via `find` over the agent-server's\n * `/api/bash/execute_bash_command`, excluding common heavy/build directories.\n * Returns paths relative to the working dir (e.g. `src/index.html`).\n *\n * Local only: the cloud API exposes no bash-exec / file-listing endpoint,\n * and the old cross-origin `/api/cloud-proxy` hop these calls relied on was\n * removed from the agent-server. See `useWorkspaceFiles` for the cloud path.\n */\nfunction useLocalWorkspaceFiles(enabled: boolean): WorkspaceFilesResult {\n const { data: conversation } = useActiveConversation();\n const runtimeIsReady = useRuntimeIsReady();\n\n const conversationId = conversation?.id;\n const conversationUrl = conversation?.conversation_url;\n const sessionApiKey = conversation?.session_api_key;\n const workingDir = conversation?.workspace?.working_dir?.trim();\n\n const query = useQuery<string[]>({\n queryKey: [\n \"workspace-files\",\n conversationId,\n conversationUrl,\n sessionApiKey,\n workingDir,\n ],\n queryFn: async () => {\n const result = await AgentServerRuntimeService.executeCommand(\n conversationUrl,\n sessionApiKey,\n buildListCommand(),\n workingDir,\n 30,\n );\n\n if (result.exit_code !== 0) {\n throw new Error(\n result.stderr?.trim() || \"Failed to list workspace files\",\n );\n }\n\n const lines = result.stdout\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter(Boolean)\n .map(normalizePath);\n\n // Defensive: keep results unique and bounded.\n return Array.from(new Set(lines)).slice(0, MAX_FILES);\n },\n enabled: enabled && runtimeIsReady && !!conversationId && !!workingDir,\n retry: false,\n staleTime: 1000 * 30,\n gcTime: 1000 * 60 * 5,\n meta: { disableToast: true },\n });\n\n return { data: query.data, isLoading: query.isLoading };\n}\n\n/**\n * Cloud-backend listing: derive the file list from the conversation's git\n * changes — the same data source the diff view uses (and the only\n * runtime-workspace transport the cloud API proxies, alongside git diff and\n * single-file read). `git status` reports created/modified/untracked files,\n * which covers the common Agent Canvas case (a fresh or agent-authored\n * workspace). It intentionally does NOT enumerate unchanged tracked files —\n * the cloud API has no full-workspace listing endpoint — so a conversation\n * attached to a large existing repo shows changed files rather than the whole\n * tree. Deleted files are dropped since they can't be opened.\n */\nfunction useCloudWorkspaceFiles(enabled: boolean): WorkspaceFilesResult {\n const gitChanges = useUnifiedGetGitChanges();\n\n const data = useMemo(() => {\n if (!enabled) return undefined;\n const paths = gitChanges.data\n .filter((change) => change.status !== \"D\")\n .map((change) => change.path);\n return Array.from(new Set(paths)).slice(0, MAX_FILES);\n }, [enabled, gitChanges.data]);\n\n return {\n data: enabled ? data : undefined,\n isLoading: enabled ? gitChanges.isLoading : false,\n };\n}\n\n/**\n * Lists the files shown in the Files tab for the active conversation.\n *\n * Local backends enumerate the full workspace tree via bash `find`. Cloud\n * backends derive the list from git changes (see `useCloudWorkspaceFiles`\n * for the rationale and its limitation), because the cloud API exposes no\n * bash-exec or file-listing endpoint.\n */\nexport function useWorkspaceFiles(): WorkspaceFilesResult {\n const { backend } = useActiveBackend();\n const isCloud = backend.kind === \"cloud\";\n\n const local = useLocalWorkspaceFiles(!isCloud);\n const cloud = useCloudWorkspaceFiles(isCloud);\n\n return isCloud ? cloud : local;\n}\n"],"mappings":"+ZAUA,IAAM,EAAY,IAQZ,EAAgB,CACpB,OACA,eACA,QACA,OACA,cACA,OACA,QACA,QACA,SACA,gBACA,cACA,SACA,gBACA,SACD,CAGD,SAAS,GAA2B,CAIlC,MAAO,cAHW,EAAc,IAAK,GAAQ,UAAU,EAAI,UAAU,CAAC,KACpE,OAEmB,CAAU,sDAAsD,IAGvF,SAAS,EAAc,EAAsB,CAE3C,OAAO,EAAK,WAAW,KAAK,CAAG,EAAK,MAAM,EAAE,CAAG,EAajD,SAAS,EAAuB,EAAwC,CACtE,GAAM,CAAE,KAAM,GAAiB,EAAA,uBAAuB,CAChD,EAAiB,EAAA,mBAAmB,CAEpC,EAAiB,GAAc,GAC/B,EAAkB,GAAc,iBAChC,EAAgB,GAAc,gBAC9B,EAAa,GAAc,WAAW,aAAa,MAAM,CAEzD,EAAQ,EAAA,SAAmB,CAC/B,SAAU,CACR,kBACA,EACA,EACA,EACA,EACD,CACD,QAAS,SAAY,CACnB,IAAM,EAAS,MAAM,EAAA,QAA0B,eAC7C,EACA,EACA,GAAkB,CAClB,EACA,GACD,CAED,GAAI,EAAO,YAAc,EACvB,MAAU,MACR,EAAO,QAAQ,MAAM,EAAI,iCAC1B,CAGH,IAAM,EAAQ,EAAO,OAClB,MAAM,QAAQ,CACd,IAAK,GAAS,EAAK,MAAM,CAAC,CAC1B,OAAO,QAAQ,CACf,IAAI,EAAc,CAGrB,OAAO,MAAM,KAAK,IAAI,IAAI,EAAM,CAAC,CAAC,MAAM,EAAG,EAAU,EAEvD,QAAS,GAAW,GAAkB,CAAC,CAAC,GAAkB,CAAC,CAAC,EAC5D,MAAO,GACP,UAAW,IAAO,GAClB,OAAQ,IAAO,GAAK,EACpB,KAAM,CAAE,aAAc,GAAM,CAC7B,CAAC,CAEF,MAAO,CAAE,KAAM,EAAM,KAAM,UAAW,EAAM,UAAW,CAczD,SAAS,EAAuB,EAAwC,CACtE,IAAM,EAAa,EAAA,yBAAyB,CAEtC,GAAA,EAAA,EAAA,aAAqB,CACzB,GAAI,CAAC,EAAS,OACd,IAAM,EAAQ,EAAW,KACtB,OAAQ,GAAW,EAAO,SAAW,IAAI,CACzC,IAAK,GAAW,EAAO,KAAK,CAC/B,OAAO,MAAM,KAAK,IAAI,IAAI,EAAM,CAAC,CAAC,MAAM,EAAG,EAAU,EACpD,CAAC,EAAS,EAAW,KAAK,CAAC,CAE9B,MAAO,CACL,KAAM,EAAU,EAAO,IAAA,GACvB,UAAW,EAAU,EAAW,UAAY,GAC7C,CAWH,SAAgB,GAA0C,CACxD,GAAM,CAAE,WAAY,EAAA,kBAAkB,CAChC,EAAU,EAAQ,OAAS,QAE3B,EAAQ,EAAuB,CAAC,EAAQ,CACxC,EAAQ,EAAuB,EAAQ,CAE7C,OAAO,EAAU,EAAQ"}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
export interface WorkspaceFilesResult {
|
|
2
|
+
data: string[] | undefined;
|
|
3
|
+
isLoading: boolean;
|
|
4
|
+
}
|
|
1
5
|
/**
|
|
2
|
-
* Lists
|
|
3
|
-
*
|
|
4
|
-
*
|
|
6
|
+
* Lists the files shown in the Files tab for the active conversation.
|
|
7
|
+
*
|
|
8
|
+
* Local backends enumerate the full workspace tree via bash `find`. Cloud
|
|
9
|
+
* backends derive the list from git changes (see `useCloudWorkspaceFiles`
|
|
10
|
+
* for the rationale and its limitation), because the cloud API exposes no
|
|
11
|
+
* bash-exec or file-listing endpoint.
|
|
5
12
|
*/
|
|
6
|
-
export declare function useWorkspaceFiles():
|
|
13
|
+
export declare function useWorkspaceFiles(): WorkspaceFilesResult;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { useQuery as e } from "../../node_modules/@tanstack/react-query/build/modern/useQuery.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import r from "
|
|
2
|
+
import { useActiveBackend as t } from "../../contexts/active-backend-context.js";
|
|
3
|
+
import { useActiveConversation as n } from "./use-active-conversation.js";
|
|
4
|
+
import { useRuntimeIsReady as r } from "../use-runtime-is-ready.js";
|
|
5
|
+
import i from "../../api/runtime-service/agent-server-runtime-service.js";
|
|
6
|
+
import { useUnifiedGetGitChanges as a } from "./use-unified-get-git-changes.js";
|
|
7
|
+
import { useMemo as o } from "react";
|
|
5
8
|
//#region src/hooks/query/use-workspace-files.ts
|
|
6
|
-
var
|
|
9
|
+
var s = 2e3, c = [
|
|
7
10
|
".git",
|
|
8
11
|
"node_modules",
|
|
9
12
|
".venv",
|
|
@@ -19,36 +22,54 @@ var i = 2e3, a = [
|
|
|
19
22
|
".parcel-cache",
|
|
20
23
|
"target"
|
|
21
24
|
];
|
|
22
|
-
function
|
|
23
|
-
return `find . \\( ${
|
|
25
|
+
function l() {
|
|
26
|
+
return `find . \\( ${c.map((e) => `-name '${e}' -prune`).join(" -o ")} \\) -o -type f -print 2>/dev/null | sort | head -n ${s}`;
|
|
24
27
|
}
|
|
25
|
-
function
|
|
28
|
+
function u(e) {
|
|
26
29
|
return e.startsWith("./") ? e.slice(2) : e;
|
|
27
30
|
}
|
|
28
|
-
function
|
|
29
|
-
let { data: a } =
|
|
30
|
-
return e({
|
|
31
|
+
function d(t) {
|
|
32
|
+
let { data: a } = n(), o = r(), c = a?.id, d = a?.conversation_url, f = a?.session_api_key, p = a?.workspace?.working_dir?.trim(), m = e({
|
|
31
33
|
queryKey: [
|
|
32
34
|
"workspace-files",
|
|
33
|
-
|
|
34
|
-
u,
|
|
35
|
+
c,
|
|
35
36
|
d,
|
|
36
|
-
f
|
|
37
|
+
f,
|
|
38
|
+
p
|
|
37
39
|
],
|
|
38
40
|
queryFn: async () => {
|
|
39
|
-
let e = await
|
|
41
|
+
let e = await i.executeCommand(d, f, l(), p, 30);
|
|
40
42
|
if (e.exit_code !== 0) throw Error(e.stderr?.trim() || "Failed to list workspace files");
|
|
41
|
-
let t = e.stdout.split(/\r?\n/).map((e) => e.trim()).filter(Boolean).map(
|
|
42
|
-
return Array.from(new Set(t)).slice(0,
|
|
43
|
+
let t = e.stdout.split(/\r?\n/).map((e) => e.trim()).filter(Boolean).map(u);
|
|
44
|
+
return Array.from(new Set(t)).slice(0, s);
|
|
43
45
|
},
|
|
44
|
-
enabled:
|
|
46
|
+
enabled: t && o && !!c && !!p,
|
|
45
47
|
retry: !1,
|
|
46
48
|
staleTime: 1e3 * 30,
|
|
47
49
|
gcTime: 1e3 * 60 * 5,
|
|
48
50
|
meta: { disableToast: !0 }
|
|
49
51
|
});
|
|
52
|
+
return {
|
|
53
|
+
data: m.data,
|
|
54
|
+
isLoading: m.isLoading
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function f(e) {
|
|
58
|
+
let t = a(), n = o(() => {
|
|
59
|
+
if (!e) return;
|
|
60
|
+
let n = t.data.filter((e) => e.status !== "D").map((e) => e.path);
|
|
61
|
+
return Array.from(new Set(n)).slice(0, s);
|
|
62
|
+
}, [e, t.data]);
|
|
63
|
+
return {
|
|
64
|
+
data: e ? n : void 0,
|
|
65
|
+
isLoading: e ? t.isLoading : !1
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function p() {
|
|
69
|
+
let { backend: e } = t(), n = e.kind === "cloud", r = d(!n), i = f(n);
|
|
70
|
+
return n ? i : r;
|
|
50
71
|
}
|
|
51
72
|
//#endregion
|
|
52
|
-
export {
|
|
73
|
+
export { p as useWorkspaceFiles };
|
|
53
74
|
|
|
54
75
|
//# sourceMappingURL=use-workspace-files.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-workspace-files.js","names":[],"sources":["../../../src/hooks/query/use-workspace-files.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\n\nimport AgentServerRuntimeService from \"#/api/runtime-service/agent-server-runtime-service\";\nimport { useActiveConversation } from \"#/hooks/query/use-active-conversation\";\nimport { useRuntimeIsReady } from \"#/hooks/use-runtime-is-ready\";\n\n// Cap the number of files we render so a giant repo doesn't freeze the UI.\nconst MAX_FILES = 2000;\n\n// Directory names that we never want to descend into when listing files.\nconst EXCLUDED_DIRS = [\n \".git\",\n \"node_modules\",\n \".venv\",\n \"venv\",\n \"__pycache__\",\n \"dist\",\n \"build\",\n \".next\",\n \".cache\",\n \".pytest_cache\",\n \".mypy_cache\",\n \".turbo\",\n \".parcel-cache\",\n \"target\",\n];\n\n// Build a `find` invocation that lists files relative to the workspace root.\nfunction buildListCommand(): string {\n const pruneExpr = EXCLUDED_DIRS.map((dir) => `-name '${dir}' -prune`).join(\n \" -o \",\n );\n return `find . \\\\( ${pruneExpr} \\\\) -o -type f -print 2>/dev/null | sort | head -n ${MAX_FILES}`;\n}\n\nfunction normalizePath(path: string): string {\n // Strip a leading \"./\" so paths render cleanly in the UI.\n return path.startsWith(\"./\") ? path.slice(2) : path;\n}\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"use-workspace-files.js","names":[],"sources":["../../../src/hooks/query/use-workspace-files.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useQuery } from \"@tanstack/react-query\";\n\nimport AgentServerRuntimeService from \"#/api/runtime-service/agent-server-runtime-service\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport { useActiveConversation } from \"#/hooks/query/use-active-conversation\";\nimport { useRuntimeIsReady } from \"#/hooks/use-runtime-is-ready\";\nimport { useUnifiedGetGitChanges } from \"#/hooks/query/use-unified-get-git-changes\";\n\n// Cap the number of files we render so a giant repo doesn't freeze the UI.\nconst MAX_FILES = 2000;\n\nexport interface WorkspaceFilesResult {\n data: string[] | undefined;\n isLoading: boolean;\n}\n\n// Directory names that we never want to descend into when listing files.\nconst EXCLUDED_DIRS = [\n \".git\",\n \"node_modules\",\n \".venv\",\n \"venv\",\n \"__pycache__\",\n \"dist\",\n \"build\",\n \".next\",\n \".cache\",\n \".pytest_cache\",\n \".mypy_cache\",\n \".turbo\",\n \".parcel-cache\",\n \"target\",\n];\n\n// Build a `find` invocation that lists files relative to the workspace root.\nfunction buildListCommand(): string {\n const pruneExpr = EXCLUDED_DIRS.map((dir) => `-name '${dir}' -prune`).join(\n \" -o \",\n );\n return `find . \\\\( ${pruneExpr} \\\\) -o -type f -print 2>/dev/null | sort | head -n ${MAX_FILES}`;\n}\n\nfunction normalizePath(path: string): string {\n // Strip a leading \"./\" so paths render cleanly in the UI.\n return path.startsWith(\"./\") ? path.slice(2) : path;\n}\n\n/**\n * Local-backend listing: enumerate every regular file beneath the active\n * conversation's working directory via `find` over the agent-server's\n * `/api/bash/execute_bash_command`, excluding common heavy/build directories.\n * Returns paths relative to the working dir (e.g. `src/index.html`).\n *\n * Local only: the cloud API exposes no bash-exec / file-listing endpoint,\n * and the old cross-origin `/api/cloud-proxy` hop these calls relied on was\n * removed from the agent-server. See `useWorkspaceFiles` for the cloud path.\n */\nfunction useLocalWorkspaceFiles(enabled: boolean): WorkspaceFilesResult {\n const { data: conversation } = useActiveConversation();\n const runtimeIsReady = useRuntimeIsReady();\n\n const conversationId = conversation?.id;\n const conversationUrl = conversation?.conversation_url;\n const sessionApiKey = conversation?.session_api_key;\n const workingDir = conversation?.workspace?.working_dir?.trim();\n\n const query = useQuery<string[]>({\n queryKey: [\n \"workspace-files\",\n conversationId,\n conversationUrl,\n sessionApiKey,\n workingDir,\n ],\n queryFn: async () => {\n const result = await AgentServerRuntimeService.executeCommand(\n conversationUrl,\n sessionApiKey,\n buildListCommand(),\n workingDir,\n 30,\n );\n\n if (result.exit_code !== 0) {\n throw new Error(\n result.stderr?.trim() || \"Failed to list workspace files\",\n );\n }\n\n const lines = result.stdout\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter(Boolean)\n .map(normalizePath);\n\n // Defensive: keep results unique and bounded.\n return Array.from(new Set(lines)).slice(0, MAX_FILES);\n },\n enabled: enabled && runtimeIsReady && !!conversationId && !!workingDir,\n retry: false,\n staleTime: 1000 * 30,\n gcTime: 1000 * 60 * 5,\n meta: { disableToast: true },\n });\n\n return { data: query.data, isLoading: query.isLoading };\n}\n\n/**\n * Cloud-backend listing: derive the file list from the conversation's git\n * changes — the same data source the diff view uses (and the only\n * runtime-workspace transport the cloud API proxies, alongside git diff and\n * single-file read). `git status` reports created/modified/untracked files,\n * which covers the common Agent Canvas case (a fresh or agent-authored\n * workspace). It intentionally does NOT enumerate unchanged tracked files —\n * the cloud API has no full-workspace listing endpoint — so a conversation\n * attached to a large existing repo shows changed files rather than the whole\n * tree. Deleted files are dropped since they can't be opened.\n */\nfunction useCloudWorkspaceFiles(enabled: boolean): WorkspaceFilesResult {\n const gitChanges = useUnifiedGetGitChanges();\n\n const data = useMemo(() => {\n if (!enabled) return undefined;\n const paths = gitChanges.data\n .filter((change) => change.status !== \"D\")\n .map((change) => change.path);\n return Array.from(new Set(paths)).slice(0, MAX_FILES);\n }, [enabled, gitChanges.data]);\n\n return {\n data: enabled ? data : undefined,\n isLoading: enabled ? gitChanges.isLoading : false,\n };\n}\n\n/**\n * Lists the files shown in the Files tab for the active conversation.\n *\n * Local backends enumerate the full workspace tree via bash `find`. Cloud\n * backends derive the list from git changes (see `useCloudWorkspaceFiles`\n * for the rationale and its limitation), because the cloud API exposes no\n * bash-exec or file-listing endpoint.\n */\nexport function useWorkspaceFiles(): WorkspaceFilesResult {\n const { backend } = useActiveBackend();\n const isCloud = backend.kind === \"cloud\";\n\n const local = useLocalWorkspaceFiles(!isCloud);\n const cloud = useCloudWorkspaceFiles(isCloud);\n\n return isCloud ? cloud : local;\n}\n"],"mappings":";;;;;;;;AAUA,IAAM,IAAY,KAQZ,IAAgB;CACpB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAGD,SAAS,IAA2B;AAIlC,QAAO,cAHW,EAAc,KAAK,MAAQ,UAAU,EAAI,UAAU,CAAC,KACpE,OAEmB,CAAU,sDAAsD;;AAGvF,SAAS,EAAc,GAAsB;AAE3C,QAAO,EAAK,WAAW,KAAK,GAAG,EAAK,MAAM,EAAE,GAAG;;AAajD,SAAS,EAAuB,GAAwC;CACtE,IAAM,EAAE,MAAM,MAAiB,GAAuB,EAChD,IAAiB,GAAmB,EAEpC,IAAiB,GAAc,IAC/B,IAAkB,GAAc,kBAChC,IAAgB,GAAc,iBAC9B,IAAa,GAAc,WAAW,aAAa,MAAM,EAEzD,IAAQ,EAAmB;EAC/B,UAAU;GACR;GACA;GACA;GACA;GACA;GACD;EACD,SAAS,YAAY;GACnB,IAAM,IAAS,MAAM,EAA0B,eAC7C,GACA,GACA,GAAkB,EAClB,GACA,GACD;AAED,OAAI,EAAO,cAAc,EACvB,OAAU,MACR,EAAO,QAAQ,MAAM,IAAI,iCAC1B;GAGH,IAAM,IAAQ,EAAO,OAClB,MAAM,QAAQ,CACd,KAAK,MAAS,EAAK,MAAM,CAAC,CAC1B,OAAO,QAAQ,CACf,IAAI,EAAc;AAGrB,UAAO,MAAM,KAAK,IAAI,IAAI,EAAM,CAAC,CAAC,MAAM,GAAG,EAAU;;EAEvD,SAAS,KAAW,KAAkB,CAAC,CAAC,KAAkB,CAAC,CAAC;EAC5D,OAAO;EACP,WAAW,MAAO;EAClB,QAAQ,MAAO,KAAK;EACpB,MAAM,EAAE,cAAc,IAAM;EAC7B,CAAC;AAEF,QAAO;EAAE,MAAM,EAAM;EAAM,WAAW,EAAM;EAAW;;AAczD,SAAS,EAAuB,GAAwC;CACtE,IAAM,IAAa,GAAyB,EAEtC,IAAO,QAAc;AACzB,MAAI,CAAC,EAAS;EACd,IAAM,IAAQ,EAAW,KACtB,QAAQ,MAAW,EAAO,WAAW,IAAI,CACzC,KAAK,MAAW,EAAO,KAAK;AAC/B,SAAO,MAAM,KAAK,IAAI,IAAI,EAAM,CAAC,CAAC,MAAM,GAAG,EAAU;IACpD,CAAC,GAAS,EAAW,KAAK,CAAC;AAE9B,QAAO;EACL,MAAM,IAAU,IAAO,KAAA;EACvB,WAAW,IAAU,EAAW,YAAY;EAC7C;;AAWH,SAAgB,IAA0C;CACxD,IAAM,EAAE,eAAY,GAAkB,EAChC,IAAU,EAAQ,SAAS,SAE3B,IAAQ,EAAuB,CAAC,EAAQ,EACxC,IAAQ,EAAuB,EAAQ;AAE7C,QAAO,IAAU,IAAQ"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require(`../_virtual/_rolldown/runtime.cjs`);const e=require(`../node_modules/@tanstack/react-query/build/modern/QueryClientProvider.cjs`),t=require(`../stores/use-event-store.cjs`),n=require(`../stores/use-workspace-mutation-counter.cjs`);let r=require(`react`);var i=new Set([`FileEditorObservation`,`StrReplaceEditorObservation`,`PlanningFileEditorObservation`]),a=new Set([`view`]);function
|
|
1
|
+
require(`../_virtual/_rolldown/runtime.cjs`);const e=require(`../node_modules/@tanstack/react-query/build/modern/QueryClientProvider.cjs`),t=require(`../stores/use-event-store.cjs`),n=require(`../stores/use-workspace-mutation-counter.cjs`);let r=require(`react`);var i=new Set([`FileEditorObservation`,`StrReplaceEditorObservation`,`PlanningFileEditorObservation`]),a=new Set([`view`]),o=new Set([`ExecuteBashObservation`,`TerminalObservation`]);function s(e){let t=e.observation;return!(!t||typeof t.kind!=`string`||!i.has(t.kind)||t.command&&a.has(t.command))}function c(e){let t=e.observation;return!!t&&typeof t.kind==`string`&&o.has(t.kind)}function l(){let i=e.useQueryClient(),a=t.useEventStore(e=>e.events),o=n.useWorkspaceMutationCounter(e=>e.bump),l=(0,r.useRef)(new Set),u=(0,r.useRef)(new WeakSet);(0,r.useEffect)(()=>{let e=!1,t=!1;for(let n of a){let r=`id`in n?n.id:void 0;(r===void 0?u.current.has(n):l.current.has(r))||(r===void 0?u.current.add(n):l.current.add(r),s(n)?e=!0:c(n)&&(t=!0))}!e&&!t||(i.invalidateQueries({queryKey:[`file_changes`]}),i.invalidateQueries({queryKey:[`file_diff`]}),e&&(i.invalidateQueries({queryKey:[`workspace-files`]}),i.invalidateQueries({queryKey:[`workspace-file-content`]}),o()))},[a,i,o])}exports.useAutoRefreshFilesOnEdit=l;
|
|
2
2
|
//# sourceMappingURL=use-auto-refresh-files-on-edit.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-auto-refresh-files-on-edit.cjs","names":[],"sources":["../../src/hooks/use-auto-refresh-files-on-edit.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\n\nimport { useEventStore, type OHEvent } from \"#/stores/use-event-store\";\nimport { useWorkspaceMutationCounter } from \"#/stores/use-workspace-mutation-counter\";\n\n// `kind` values we treat as a file-mutation observation.\nconst FILE_EDIT_OBSERVATION_KINDS = new Set([\n \"FileEditorObservation\",\n \"StrReplaceEditorObservation\",\n \"PlanningFileEditorObservation\",\n]);\n\n// Commands on the str-replace-editor family that don't change anything on\n// disk. We don't want to invalidate caches for those.\nconst READ_ONLY_COMMANDS = new Set([\"view\"]);\n\nfunction isFileMutationObservation(event: OHEvent): boolean {\n // ObservationEvents have `source: \"environment\"` and an `observation`\n // field — narrow to that shape without pulling in the whole event union.\n const obs = (event as { observation?: { kind?: string; command?: string } })\n .observation;\n if (!obs || typeof obs.kind !== \"string\") return false;\n if (!FILE_EDIT_OBSERVATION_KINDS.has(obs.kind)) return false;\n if (obs.command && READ_ONLY_COMMANDS.has(obs.command)) return false;\n return true;\n}\n\n/**\n * Watches the conversation event stream and invalidates the workspace file\n * queries whenever the agent commits a file-editor mutation (create / edit /\n * insert / undo_edit). This keeps the Files tab's list, content view and\n * diff view in sync with what the agent has actually written to disk,\n * without requiring the user to click refresh manually.\n *\n * Mount this hook inside any component that should drive auto-refresh —\n * the Files tab is the obvious caller. Multiple mounts are safe because\n * React Query coalesces overlapping invalidations.\n */\nexport function useAutoRefreshFilesOnEdit(): void {\n const queryClient = useQueryClient();\n const events = useEventStore((state) => state.events);\n const bumpWorkspaceMutationCounter = useWorkspaceMutationCounter(\n (state) => state.bump,\n );\n\n // Track which events we've already reacted to. Two parallel stores:\n //\n // 1. `processedIdsRef` — keys events that *have* an id. The event store\n // re-sorts on insert when out-of-order events arrive (an older event\n // can land *between* two newer ones already in the array), so we\n // cannot use a `slice(processedCount)` trick — it would miss a\n // late-arriving older event because the array length grew but the\n // tail we just diffed didn't contain it. Using a Set of ids is O(n)\n // per render in the worst case but small in practice and immune to\n // reordering. Type matches the event store's own dedup set\n // (`Set<string | number>` in `use-event-store.ts`) so a stray\n // numeric id (legacy server payload, hand-crafted test event, …)\n // can't sneak past.\n //\n // 2. `processedEventsRef` — keys events that have NO id. We cannot put\n // a literal `undefined` into the id Set: that would make the second,\n // third, … id-less arrival collide on the same `undefined` key and\n // silently skip them. We also cannot just skip dedup for id-less\n // events: the events array is rebuilt on every store mutation but\n // its element references are stable, so the same id-less event\n // appears in the array forever and would re-bump the workspace\n // mutation counter on every subsequent re-render. Keying by object\n // reference (via a WeakSet) gives us \"process each id-less event\n // exactly once\" — durable, free, no held-onto memory after the\n // store clears.\n const processedIdsRef = useRef<Set<string | number>>(new Set());\n const processedEventsRef = useRef<WeakSet<OHEvent>>(new WeakSet());\n\n useEffect(() => {\n
|
|
1
|
+
{"version":3,"file":"use-auto-refresh-files-on-edit.cjs","names":[],"sources":["../../src/hooks/use-auto-refresh-files-on-edit.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\n\nimport { useEventStore, type OHEvent } from \"#/stores/use-event-store\";\nimport { useWorkspaceMutationCounter } from \"#/stores/use-workspace-mutation-counter\";\n\n// `kind` values we treat as a file-mutation observation.\nconst FILE_EDIT_OBSERVATION_KINDS = new Set([\n \"FileEditorObservation\",\n \"StrReplaceEditorObservation\",\n \"PlanningFileEditorObservation\",\n]);\n\n// Commands on the str-replace-editor family that don't change anything on\n// disk. We don't want to invalidate caches for those.\nconst READ_ONLY_COMMANDS = new Set([\"view\"]);\n\n// Bash observations are how `git commit` / `git push` (and any file edit\n// done through the shell) arrive, so they must refresh the Diff view too.\n// Legacy agent-servers emit `ExecuteBashObservation`; the current SDK\n// emits `TerminalObservation`.\nconst BASH_OBSERVATION_KINDS = new Set([\n \"ExecuteBashObservation\",\n \"TerminalObservation\",\n]);\n\nfunction isFileMutationObservation(event: OHEvent): boolean {\n // ObservationEvents have `source: \"environment\"` and an `observation`\n // field — narrow to that shape without pulling in the whole event union.\n const obs = (event as { observation?: { kind?: string; command?: string } })\n .observation;\n if (!obs || typeof obs.kind !== \"string\") return false;\n if (!FILE_EDIT_OBSERVATION_KINDS.has(obs.kind)) return false;\n if (obs.command && READ_ONLY_COMMANDS.has(obs.command)) return false;\n return true;\n}\n\n// Note: `READ_ONLY_COMMANDS` intentionally doesn't apply here — for bash\n// observations `command` is the whole shell command line, not a\n// file-editor sub-command.\nfunction isBashObservation(event: OHEvent): boolean {\n const obs = (event as { observation?: { kind?: string } }).observation;\n return (\n !!obs &&\n typeof obs.kind === \"string\" &&\n BASH_OBSERVATION_KINDS.has(obs.kind)\n );\n}\n\n/**\n * Watches the conversation event stream and invalidates the workspace file\n * queries whenever the agent commits a file-editor mutation (create / edit /\n * insert / undo_edit). This keeps the Files tab's list, content view and\n * diff view in sync with what the agent has actually written to disk,\n * without requiring the user to click refresh manually.\n *\n * Bash observations also refresh the git-diff queries (`file_changes` /\n * `file_diff`) — a `git commit` or `git push` changes what the Diff view\n * should display, and shell commands can edit files too. They deliberately\n * do NOT touch the workspace file queries or the workspace mutation\n * counter: bumping the counter reloads canvas iframes, and doing that for\n * every shell command the agent runs would cause constant flicker.\n * Invalidation only refetches actively-mounted queries, so the cost is\n * limited to when the Files tab is open.\n *\n * Mount this hook inside any component that should drive auto-refresh —\n * the Files tab is the obvious caller. Multiple mounts are safe because\n * React Query coalesces overlapping invalidations.\n */\nexport function useAutoRefreshFilesOnEdit(): void {\n const queryClient = useQueryClient();\n const events = useEventStore((state) => state.events);\n const bumpWorkspaceMutationCounter = useWorkspaceMutationCounter(\n (state) => state.bump,\n );\n\n // Track which events we've already reacted to. Two parallel stores:\n //\n // 1. `processedIdsRef` — keys events that *have* an id. The event store\n // re-sorts on insert when out-of-order events arrive (an older event\n // can land *between* two newer ones already in the array), so we\n // cannot use a `slice(processedCount)` trick — it would miss a\n // late-arriving older event because the array length grew but the\n // tail we just diffed didn't contain it. Using a Set of ids is O(n)\n // per render in the worst case but small in practice and immune to\n // reordering. Type matches the event store's own dedup set\n // (`Set<string | number>` in `use-event-store.ts`) so a stray\n // numeric id (legacy server payload, hand-crafted test event, …)\n // can't sneak past.\n //\n // 2. `processedEventsRef` — keys events that have NO id. We cannot put\n // a literal `undefined` into the id Set: that would make the second,\n // third, … id-less arrival collide on the same `undefined` key and\n // silently skip them. We also cannot just skip dedup for id-less\n // events: the events array is rebuilt on every store mutation but\n // its element references are stable, so the same id-less event\n // appears in the array forever and would re-bump the workspace\n // mutation counter on every subsequent re-render. Keying by object\n // reference (via a WeakSet) gives us \"process each id-less event\n // exactly once\" — durable, free, no held-onto memory after the\n // store clears.\n const processedIdsRef = useRef<Set<string | number>>(new Set());\n const processedEventsRef = useRef<WeakSet<OHEvent>>(new WeakSet());\n\n useEffect(() => {\n let hasNewFileEdits = false;\n let hasNewBashCommands = false;\n for (const event of events) {\n const id: string | number | undefined =\n \"id\" in event ? event.id : undefined;\n const alreadyProcessed =\n id !== undefined\n ? processedIdsRef.current.has(id)\n : processedEventsRef.current.has(event);\n // Inverted predicate so we avoid `continue` (banned by repo lint).\n if (!alreadyProcessed) {\n if (id !== undefined) {\n processedIdsRef.current.add(id);\n } else {\n processedEventsRef.current.add(event);\n }\n if (isFileMutationObservation(event)) hasNewFileEdits = true;\n else if (isBashObservation(event)) hasNewBashCommands = true;\n }\n }\n\n if (!hasNewFileEdits && !hasNewBashCommands) return;\n\n // Editor and bash mutations both change what the git Diff view shows —\n // including the per-file diff content of an already-expanded file.\n queryClient.invalidateQueries({ queryKey: [\"file_changes\"] });\n queryClient.invalidateQueries({ queryKey: [\"file_diff\"] });\n\n if (hasNewFileEdits) {\n queryClient.invalidateQueries({ queryKey: [\"workspace-files\"] });\n queryClient.invalidateQueries({ queryKey: [\"workspace-file-content\"] });\n // Force iframes / <img> tags pointing at the static workspace\n // fileserver to re-fetch. Without this they happily keep showing the\n // stale (browser-cached) bytes even after the agent has rewritten the\n // file on disk — e.g. tweaking style.css would silently have no\n // visible effect on the rendered index.html until the user reloaded\n // the whole canvas.\n bumpWorkspaceMutationCounter();\n }\n }, [events, queryClient, bumpWorkspaceMutationCounter]);\n}\n"],"mappings":"uQAOA,IAAM,EAA8B,IAAI,IAAI,CAC1C,wBACA,8BACA,gCACD,CAAC,CAII,EAAqB,IAAI,IAAI,CAAC,OAAO,CAAC,CAMtC,EAAyB,IAAI,IAAI,CACrC,yBACA,sBACD,CAAC,CAEF,SAAS,EAA0B,EAAyB,CAG1D,IAAM,EAAO,EACV,YAIH,MADA,EAFI,CAAC,GAAO,OAAO,EAAI,MAAS,UAC5B,CAAC,EAA4B,IAAI,EAAI,KAAK,EAC1C,EAAI,SAAW,EAAmB,IAAI,EAAI,QAAQ,EAOxD,SAAS,EAAkB,EAAyB,CAClD,IAAM,EAAO,EAA8C,YAC3D,MACE,CAAC,CAAC,GACF,OAAO,EAAI,MAAS,UACpB,EAAuB,IAAI,EAAI,KAAK,CAwBxC,SAAgB,GAAkC,CAChD,IAAM,EAAc,EAAA,gBAAgB,CAC9B,EAAS,EAAA,cAAe,GAAU,EAAM,OAAO,CAC/C,EAA+B,EAAA,4BAClC,GAAU,EAAM,KAClB,CA2BK,GAAA,EAAA,EAAA,QAA+C,IAAI,IAAM,CACzD,GAAA,EAAA,EAAA,QAA8C,IAAI,QAAU,EAElE,EAAA,EAAA,eAAgB,CACd,IAAI,EAAkB,GAClB,EAAqB,GACzB,IAAK,IAAM,KAAS,EAAQ,CAC1B,IAAM,EACJ,OAAQ,EAAQ,EAAM,GAAK,IAAA,IAE3B,IAAO,IAAA,GAEH,EAAmB,QAAQ,IAAI,EAAM,CADrC,EAAgB,QAAQ,IAAI,EAAG,IAI/B,IAAO,IAAA,GAGT,EAAmB,QAAQ,IAAI,EAAM,CAFrC,EAAgB,QAAQ,IAAI,EAAG,CAI7B,EAA0B,EAAM,CAAE,EAAkB,GAC/C,EAAkB,EAAM,GAAE,EAAqB,KAIxD,CAAC,GAAmB,CAAC,IAIzB,EAAY,kBAAkB,CAAE,SAAU,CAAC,eAAe,CAAE,CAAC,CAC7D,EAAY,kBAAkB,CAAE,SAAU,CAAC,YAAY,CAAE,CAAC,CAEtD,IACF,EAAY,kBAAkB,CAAE,SAAU,CAAC,kBAAkB,CAAE,CAAC,CAChE,EAAY,kBAAkB,CAAE,SAAU,CAAC,yBAAyB,CAAE,CAAC,CAOvE,GAA8B,IAE/B,CAAC,EAAQ,EAAa,EAA6B,CAAC"}
|
|
@@ -5,6 +5,15 @@
|
|
|
5
5
|
* diff view in sync with what the agent has actually written to disk,
|
|
6
6
|
* without requiring the user to click refresh manually.
|
|
7
7
|
*
|
|
8
|
+
* Bash observations also refresh the git-diff queries (`file_changes` /
|
|
9
|
+
* `file_diff`) — a `git commit` or `git push` changes what the Diff view
|
|
10
|
+
* should display, and shell commands can edit files too. They deliberately
|
|
11
|
+
* do NOT touch the workspace file queries or the workspace mutation
|
|
12
|
+
* counter: bumping the counter reloads canvas iframes, and doing that for
|
|
13
|
+
* every shell command the agent runs would cause constant flicker.
|
|
14
|
+
* Invalidation only refetches actively-mounted queries, so the cost is
|
|
15
|
+
* limited to when the Files tab is open.
|
|
16
|
+
*
|
|
8
17
|
* Mount this hook inside any component that should drive auto-refresh —
|
|
9
18
|
* the Files tab is the obvious caller. Multiple mounts are safe because
|
|
10
19
|
* React Query coalesces overlapping invalidations.
|
|
@@ -7,27 +7,31 @@ var a = new Set([
|
|
|
7
7
|
"FileEditorObservation",
|
|
8
8
|
"StrReplaceEditorObservation",
|
|
9
9
|
"PlanningFileEditorObservation"
|
|
10
|
-
]), o = new Set(["view"]);
|
|
11
|
-
function
|
|
10
|
+
]), o = new Set(["view"]), s = new Set(["ExecuteBashObservation", "TerminalObservation"]);
|
|
11
|
+
function c(e) {
|
|
12
12
|
let t = e.observation;
|
|
13
13
|
return !(!t || typeof t.kind != "string" || !a.has(t.kind) || t.command && o.has(t.command));
|
|
14
14
|
}
|
|
15
|
-
function
|
|
16
|
-
let
|
|
15
|
+
function l(e) {
|
|
16
|
+
let t = e.observation;
|
|
17
|
+
return !!t && typeof t.kind == "string" && s.has(t.kind);
|
|
18
|
+
}
|
|
19
|
+
function u() {
|
|
20
|
+
let a = e(), o = t((e) => e.events), s = n((e) => e.bump), u = i(/* @__PURE__ */ new Set()), d = i(/* @__PURE__ */ new WeakSet());
|
|
17
21
|
r(() => {
|
|
18
|
-
let e =
|
|
19
|
-
for (let
|
|
20
|
-
let
|
|
21
|
-
(
|
|
22
|
+
let e = !1, t = !1;
|
|
23
|
+
for (let n of o) {
|
|
24
|
+
let r = "id" in n ? n.id : void 0;
|
|
25
|
+
(r === void 0 ? d.current.has(n) : u.current.has(r)) || (r === void 0 ? d.current.add(n) : u.current.add(r), c(n) ? e = !0 : l(n) && (t = !0));
|
|
22
26
|
}
|
|
23
|
-
e
|
|
27
|
+
!e && !t || (a.invalidateQueries({ queryKey: ["file_changes"] }), a.invalidateQueries({ queryKey: ["file_diff"] }), e && (a.invalidateQueries({ queryKey: ["workspace-files"] }), a.invalidateQueries({ queryKey: ["workspace-file-content"] }), s()));
|
|
24
28
|
}, [
|
|
25
29
|
o,
|
|
26
30
|
a,
|
|
27
|
-
|
|
31
|
+
s
|
|
28
32
|
]);
|
|
29
33
|
}
|
|
30
34
|
//#endregion
|
|
31
|
-
export {
|
|
35
|
+
export { u as useAutoRefreshFilesOnEdit };
|
|
32
36
|
|
|
33
37
|
//# sourceMappingURL=use-auto-refresh-files-on-edit.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-auto-refresh-files-on-edit.js","names":[],"sources":["../../src/hooks/use-auto-refresh-files-on-edit.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\n\nimport { useEventStore, type OHEvent } from \"#/stores/use-event-store\";\nimport { useWorkspaceMutationCounter } from \"#/stores/use-workspace-mutation-counter\";\n\n// `kind` values we treat as a file-mutation observation.\nconst FILE_EDIT_OBSERVATION_KINDS = new Set([\n \"FileEditorObservation\",\n \"StrReplaceEditorObservation\",\n \"PlanningFileEditorObservation\",\n]);\n\n// Commands on the str-replace-editor family that don't change anything on\n// disk. We don't want to invalidate caches for those.\nconst READ_ONLY_COMMANDS = new Set([\"view\"]);\n\nfunction isFileMutationObservation(event: OHEvent): boolean {\n // ObservationEvents have `source: \"environment\"` and an `observation`\n // field — narrow to that shape without pulling in the whole event union.\n const obs = (event as { observation?: { kind?: string; command?: string } })\n .observation;\n if (!obs || typeof obs.kind !== \"string\") return false;\n if (!FILE_EDIT_OBSERVATION_KINDS.has(obs.kind)) return false;\n if (obs.command && READ_ONLY_COMMANDS.has(obs.command)) return false;\n return true;\n}\n\n/**\n * Watches the conversation event stream and invalidates the workspace file\n * queries whenever the agent commits a file-editor mutation (create / edit /\n * insert / undo_edit). This keeps the Files tab's list, content view and\n * diff view in sync with what the agent has actually written to disk,\n * without requiring the user to click refresh manually.\n *\n * Mount this hook inside any component that should drive auto-refresh —\n * the Files tab is the obvious caller. Multiple mounts are safe because\n * React Query coalesces overlapping invalidations.\n */\nexport function useAutoRefreshFilesOnEdit(): void {\n const queryClient = useQueryClient();\n const events = useEventStore((state) => state.events);\n const bumpWorkspaceMutationCounter = useWorkspaceMutationCounter(\n (state) => state.bump,\n );\n\n // Track which events we've already reacted to. Two parallel stores:\n //\n // 1. `processedIdsRef` — keys events that *have* an id. The event store\n // re-sorts on insert when out-of-order events arrive (an older event\n // can land *between* two newer ones already in the array), so we\n // cannot use a `slice(processedCount)` trick — it would miss a\n // late-arriving older event because the array length grew but the\n // tail we just diffed didn't contain it. Using a Set of ids is O(n)\n // per render in the worst case but small in practice and immune to\n // reordering. Type matches the event store's own dedup set\n // (`Set<string | number>` in `use-event-store.ts`) so a stray\n // numeric id (legacy server payload, hand-crafted test event, …)\n // can't sneak past.\n //\n // 2. `processedEventsRef` — keys events that have NO id. We cannot put\n // a literal `undefined` into the id Set: that would make the second,\n // third, … id-less arrival collide on the same `undefined` key and\n // silently skip them. We also cannot just skip dedup for id-less\n // events: the events array is rebuilt on every store mutation but\n // its element references are stable, so the same id-less event\n // appears in the array forever and would re-bump the workspace\n // mutation counter on every subsequent re-render. Keying by object\n // reference (via a WeakSet) gives us \"process each id-less event\n // exactly once\" — durable, free, no held-onto memory after the\n // store clears.\n const processedIdsRef = useRef<Set<string | number>>(new Set());\n const processedEventsRef = useRef<WeakSet<OHEvent>>(new WeakSet());\n\n useEffect(() => {\n
|
|
1
|
+
{"version":3,"file":"use-auto-refresh-files-on-edit.js","names":[],"sources":["../../src/hooks/use-auto-refresh-files-on-edit.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\n\nimport { useEventStore, type OHEvent } from \"#/stores/use-event-store\";\nimport { useWorkspaceMutationCounter } from \"#/stores/use-workspace-mutation-counter\";\n\n// `kind` values we treat as a file-mutation observation.\nconst FILE_EDIT_OBSERVATION_KINDS = new Set([\n \"FileEditorObservation\",\n \"StrReplaceEditorObservation\",\n \"PlanningFileEditorObservation\",\n]);\n\n// Commands on the str-replace-editor family that don't change anything on\n// disk. We don't want to invalidate caches for those.\nconst READ_ONLY_COMMANDS = new Set([\"view\"]);\n\n// Bash observations are how `git commit` / `git push` (and any file edit\n// done through the shell) arrive, so they must refresh the Diff view too.\n// Legacy agent-servers emit `ExecuteBashObservation`; the current SDK\n// emits `TerminalObservation`.\nconst BASH_OBSERVATION_KINDS = new Set([\n \"ExecuteBashObservation\",\n \"TerminalObservation\",\n]);\n\nfunction isFileMutationObservation(event: OHEvent): boolean {\n // ObservationEvents have `source: \"environment\"` and an `observation`\n // field — narrow to that shape without pulling in the whole event union.\n const obs = (event as { observation?: { kind?: string; command?: string } })\n .observation;\n if (!obs || typeof obs.kind !== \"string\") return false;\n if (!FILE_EDIT_OBSERVATION_KINDS.has(obs.kind)) return false;\n if (obs.command && READ_ONLY_COMMANDS.has(obs.command)) return false;\n return true;\n}\n\n// Note: `READ_ONLY_COMMANDS` intentionally doesn't apply here — for bash\n// observations `command` is the whole shell command line, not a\n// file-editor sub-command.\nfunction isBashObservation(event: OHEvent): boolean {\n const obs = (event as { observation?: { kind?: string } }).observation;\n return (\n !!obs &&\n typeof obs.kind === \"string\" &&\n BASH_OBSERVATION_KINDS.has(obs.kind)\n );\n}\n\n/**\n * Watches the conversation event stream and invalidates the workspace file\n * queries whenever the agent commits a file-editor mutation (create / edit /\n * insert / undo_edit). This keeps the Files tab's list, content view and\n * diff view in sync with what the agent has actually written to disk,\n * without requiring the user to click refresh manually.\n *\n * Bash observations also refresh the git-diff queries (`file_changes` /\n * `file_diff`) — a `git commit` or `git push` changes what the Diff view\n * should display, and shell commands can edit files too. They deliberately\n * do NOT touch the workspace file queries or the workspace mutation\n * counter: bumping the counter reloads canvas iframes, and doing that for\n * every shell command the agent runs would cause constant flicker.\n * Invalidation only refetches actively-mounted queries, so the cost is\n * limited to when the Files tab is open.\n *\n * Mount this hook inside any component that should drive auto-refresh —\n * the Files tab is the obvious caller. Multiple mounts are safe because\n * React Query coalesces overlapping invalidations.\n */\nexport function useAutoRefreshFilesOnEdit(): void {\n const queryClient = useQueryClient();\n const events = useEventStore((state) => state.events);\n const bumpWorkspaceMutationCounter = useWorkspaceMutationCounter(\n (state) => state.bump,\n );\n\n // Track which events we've already reacted to. Two parallel stores:\n //\n // 1. `processedIdsRef` — keys events that *have* an id. The event store\n // re-sorts on insert when out-of-order events arrive (an older event\n // can land *between* two newer ones already in the array), so we\n // cannot use a `slice(processedCount)` trick — it would miss a\n // late-arriving older event because the array length grew but the\n // tail we just diffed didn't contain it. Using a Set of ids is O(n)\n // per render in the worst case but small in practice and immune to\n // reordering. Type matches the event store's own dedup set\n // (`Set<string | number>` in `use-event-store.ts`) so a stray\n // numeric id (legacy server payload, hand-crafted test event, …)\n // can't sneak past.\n //\n // 2. `processedEventsRef` — keys events that have NO id. We cannot put\n // a literal `undefined` into the id Set: that would make the second,\n // third, … id-less arrival collide on the same `undefined` key and\n // silently skip them. We also cannot just skip dedup for id-less\n // events: the events array is rebuilt on every store mutation but\n // its element references are stable, so the same id-less event\n // appears in the array forever and would re-bump the workspace\n // mutation counter on every subsequent re-render. Keying by object\n // reference (via a WeakSet) gives us \"process each id-less event\n // exactly once\" — durable, free, no held-onto memory after the\n // store clears.\n const processedIdsRef = useRef<Set<string | number>>(new Set());\n const processedEventsRef = useRef<WeakSet<OHEvent>>(new WeakSet());\n\n useEffect(() => {\n let hasNewFileEdits = false;\n let hasNewBashCommands = false;\n for (const event of events) {\n const id: string | number | undefined =\n \"id\" in event ? event.id : undefined;\n const alreadyProcessed =\n id !== undefined\n ? processedIdsRef.current.has(id)\n : processedEventsRef.current.has(event);\n // Inverted predicate so we avoid `continue` (banned by repo lint).\n if (!alreadyProcessed) {\n if (id !== undefined) {\n processedIdsRef.current.add(id);\n } else {\n processedEventsRef.current.add(event);\n }\n if (isFileMutationObservation(event)) hasNewFileEdits = true;\n else if (isBashObservation(event)) hasNewBashCommands = true;\n }\n }\n\n if (!hasNewFileEdits && !hasNewBashCommands) return;\n\n // Editor and bash mutations both change what the git Diff view shows —\n // including the per-file diff content of an already-expanded file.\n queryClient.invalidateQueries({ queryKey: [\"file_changes\"] });\n queryClient.invalidateQueries({ queryKey: [\"file_diff\"] });\n\n if (hasNewFileEdits) {\n queryClient.invalidateQueries({ queryKey: [\"workspace-files\"] });\n queryClient.invalidateQueries({ queryKey: [\"workspace-file-content\"] });\n // Force iframes / <img> tags pointing at the static workspace\n // fileserver to re-fetch. Without this they happily keep showing the\n // stale (browser-cached) bytes even after the agent has rewritten the\n // file on disk — e.g. tweaking style.css would silently have no\n // visible effect on the rendered index.html until the user reloaded\n // the whole canvas.\n bumpWorkspaceMutationCounter();\n }\n }, [events, queryClient, bumpWorkspaceMutationCounter]);\n}\n"],"mappings":";;;;;AAOA,IAAM,IAA8B,IAAI,IAAI;CAC1C;CACA;CACA;CACD,CAAC,EAII,IAAqB,IAAI,IAAI,CAAC,OAAO,CAAC,EAMtC,IAAyB,IAAI,IAAI,CACrC,0BACA,sBACD,CAAC;AAEF,SAAS,EAA0B,GAAyB;CAG1D,IAAM,IAAO,EACV;AAIH,QADA,EAFI,CAAC,KAAO,OAAO,EAAI,QAAS,YAC5B,CAAC,EAA4B,IAAI,EAAI,KAAK,IAC1C,EAAI,WAAW,EAAmB,IAAI,EAAI,QAAQ;;AAOxD,SAAS,EAAkB,GAAyB;CAClD,IAAM,IAAO,EAA8C;AAC3D,QACE,CAAC,CAAC,KACF,OAAO,EAAI,QAAS,YACpB,EAAuB,IAAI,EAAI,KAAK;;AAwBxC,SAAgB,IAAkC;CAChD,IAAM,IAAc,GAAgB,EAC9B,IAAS,GAAe,MAAU,EAAM,OAAO,EAC/C,IAA+B,GAClC,MAAU,EAAM,KAClB,EA2BK,IAAkB,kBAA6B,IAAI,KAAK,CAAC,EACzD,IAAqB,kBAAyB,IAAI,SAAS,CAAC;AAElE,SAAgB;EACd,IAAI,IAAkB,IAClB,IAAqB;AACzB,OAAK,IAAM,KAAS,GAAQ;GAC1B,IAAM,IACJ,QAAQ,IAAQ,EAAM,KAAK,KAAA;AAM7B,IAJE,MAAO,KAAA,IAEH,EAAmB,QAAQ,IAAI,EAAM,GADrC,EAAgB,QAAQ,IAAI,EAAG,MAI/B,MAAO,KAAA,IAGT,EAAmB,QAAQ,IAAI,EAAM,GAFrC,EAAgB,QAAQ,IAAI,EAAG,EAI7B,EAA0B,EAAM,GAAE,IAAkB,KAC/C,EAAkB,EAAM,KAAE,IAAqB;;AAIxD,GAAC,KAAmB,CAAC,MAIzB,EAAY,kBAAkB,EAAE,UAAU,CAAC,eAAe,EAAE,CAAC,EAC7D,EAAY,kBAAkB,EAAE,UAAU,CAAC,YAAY,EAAE,CAAC,EAEtD,MACF,EAAY,kBAAkB,EAAE,UAAU,CAAC,kBAAkB,EAAE,CAAC,EAChE,EAAY,kBAAkB,EAAE,UAAU,CAAC,yBAAyB,EAAE,CAAC,EAOvE,GAA8B;IAE/B;EAAC;EAAQ;EAAa;EAA6B,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require(`../_virtual/_rolldown/runtime.cjs`);const e=require(`../node_modules/posthog-js/react/dist/esm/index.cjs`),t=require(`./query/use-settings.cjs`);var n=()=>{let n=e.usePostHog(),{data:r}=t.useSettings(),i={current_url:window.location.href,user_email:r?.email||r?.git_user_email||null},a=(e,t={})=>{!n||r?.user_consents_to_analytics!==!0||n.capture(e,{...t,...i})};return{trackLoginButtonClick:({provider:e})=>{a(`login_button_clicked`,{provider:e})},trackConversationCreated:({conversationId:e,taskId:t,hasRepository:n,gitProvider:r,hasWorkspace:i,workspaceMode:o,hasInitialQuery:s,agentType:c,hasParentConversation:l,entryPoint:u})=>{a(`conversation_created`,{conversation_id:e,task_id:t,is_start_task:e.startsWith(`task-`),has_repository:n,git_provider:r,has_workspace:i,workspace_mode:o,has_initial_query:s,agent_type:c,has_parent_conversation:l,entry_point:u})},trackPushButtonClick:()=>{a(`push_button_clicked`)},trackPullButtonClick:()=>{a(`pull_button_clicked`)},trackCreatePrButtonClick:()=>{a(`create_pr_button_clicked`)},trackUserSignupCompleted:()=>{a(`user_signup_completed`,{signup_timestamp:new Date().toISOString()})},trackPrebuiltAutomationEnabled:({automationId:e,automationName:t,automationCategory:n})=>{a(`prebuilt_automation_enabled`,{automation_id:e,automation_name:t,automation_category:n})},trackInitialQuerySubmitted:({entryPoint:e,queryCharacterLength:t,replayJsonSize:n})=>{a(`initial_query_submitted`,{entry_point:e,query_character_length:t,replay_json_size:n})},trackUserMessageSent:({sessionMessageCount:e,currentMessageLength:t})=>{a(`user_message_sent`,{session_message_count:e,current_message_length:t})},trackDownloadVsCodeButtonClicked:()=>{a(`download_via_vscode_button_clicked`)},trackSettingsSaved:({llmModel:e,llmApiKeySet:t,searchApiKeySet:n,remoteRuntimeResourceFactor:r})=>{a(`settings_saved`,{LLM_MODEL:e,LLM_API_KEY_SET:t,SEARCH_API_KEY_SET:n,REMOTE_RUNTIME_RESOURCE_FACTOR:r})},trackMcpConfigUpdated:({sseServersCount:e,stdioServersCount:t})=>{a(`mcp_config_updated`,{has_mcp_config:!0,sse_servers_count:e,stdio_servers_count:t})},trackDownloadTrajectoryButtonClicked:()=>{a(`download_trajectory_button_clicked`)},trackAutomationCreated:({backendKind:e})=>{a(`automation_created`,{backend_kind:e})},trackAutomationExecuted:({backendKind:e})=>{a(`automation_executed`,{backend_kind:e})},trackAutomationDeleted:({backendKind:e})=>{a(`automation_deleted`,{backend_kind:e})},trackAutomationDeactivated:({backendKind:e})=>{a(`automation_deactivated`,{backend_kind:e})},trackAutomationEdited:({backendKind:e})=>{a(`automation_edited`,{backend_kind:e})},trackBackendAdded:({backendKind:e,connectionMethod:t,isOpenhandsCloud:n,isCustomHost:r,hasApiKey:i,source:o})=>{a(`backend_added`,{backend_kind:e,connection_method:t,is_openhands_cloud:n,is_custom_host:r,has_api_key:i,source:o})},trackOnboardingStarted:()=>{a(`onboarding_started`)},trackOnboardingStepViewed:({step:e,stepIndex:t,totalSteps:n,agent:r})=>{a(`onboarding_step_viewed`,{step:e,step_index:t,total_steps:n,agent:r})},trackOnboardingCompleted:({agent:e})=>{a(`onboarding_completed`,{agent:e})},trackOnboardingSkipped:({step:e,stepIndex:t,totalSteps:n,agent:r})=>{a(`onboarding_skipped`,{step:e,step_index:t,total_steps:n,agent:r})}}};exports.useTracking=n;
|
|
1
|
+
require(`../_virtual/_rolldown/runtime.cjs`);const e=require(`../node_modules/posthog-js/react/dist/esm/index.cjs`),t=require(`./query/use-settings.cjs`);var n=()=>{let n=e.usePostHog(),{data:r}=t.useSettings(),i={current_url:window.location.href,user_email:r?.email||r?.git_user_email||null},a=(e,t={})=>{!n||r?.user_consents_to_analytics!==!0||n.capture(e,{...t,...i})};return{trackLoginButtonClick:({provider:e})=>{a(`login_button_clicked`,{provider:e})},trackConversationCreated:({conversationId:e,taskId:t,hasRepository:n,gitProvider:r,hasWorkspace:i,workspaceMode:o,hasInitialQuery:s,agentType:c,hasParentConversation:l,entryPoint:u})=>{a(`conversation_created`,{conversation_id:e,task_id:t,is_start_task:e.startsWith(`task-`),has_repository:n,git_provider:r,has_workspace:i,workspace_mode:o,has_initial_query:s,agent_type:c,has_parent_conversation:l,entry_point:u})},trackPushButtonClick:()=>{a(`push_button_clicked`)},trackPullButtonClick:()=>{a(`pull_button_clicked`)},trackCreatePrButtonClick:()=>{a(`create_pr_button_clicked`)},trackUserSignupCompleted:()=>{a(`user_signup_completed`,{signup_timestamp:new Date().toISOString()})},trackPrebuiltAutomationEnabled:({automationId:e,automationName:t,automationCategory:n})=>{a(`prebuilt_automation_enabled`,{automation_id:e,automation_name:t,automation_category:n})},trackInitialQuerySubmitted:({entryPoint:e,queryCharacterLength:t,replayJsonSize:n})=>{a(`initial_query_submitted`,{entry_point:e,query_character_length:t,replay_json_size:n})},trackUserMessageSent:({sessionMessageCount:e,currentMessageLength:t})=>{a(`user_message_sent`,{session_message_count:e,current_message_length:t})},trackDownloadVsCodeButtonClicked:()=>{a(`download_via_vscode_button_clicked`)},trackSettingsSaved:({llmModel:e,llmApiKeySet:t,searchApiKeySet:n,remoteRuntimeResourceFactor:r})=>{a(`settings_saved`,{LLM_MODEL:e,LLM_API_KEY_SET:t,SEARCH_API_KEY_SET:n,REMOTE_RUNTIME_RESOURCE_FACTOR:r})},trackMcpConfigUpdated:({sseServersCount:e,stdioServersCount:t})=>{a(`mcp_config_updated`,{has_mcp_config:!0,sse_servers_count:e,stdio_servers_count:t})},trackDownloadTrajectoryButtonClicked:()=>{a(`download_trajectory_button_clicked`)},trackConversationExported:e=>{a(`conversation_exported`,{format:e})},trackAutomationCreated:({backendKind:e})=>{a(`automation_created`,{backend_kind:e})},trackAutomationExecuted:({backendKind:e})=>{a(`automation_executed`,{backend_kind:e})},trackAutomationDeleted:({backendKind:e})=>{a(`automation_deleted`,{backend_kind:e})},trackAutomationDeactivated:({backendKind:e})=>{a(`automation_deactivated`,{backend_kind:e})},trackAutomationEdited:({backendKind:e})=>{a(`automation_edited`,{backend_kind:e})},trackAutomationExported:({backendKind:e})=>{a(`automation_exported`,{backend_kind:e})},trackAutomationImported:({backendKind:e})=>{a(`automation_imported`,{backend_kind:e})},trackBackendAdded:({backendKind:e,connectionMethod:t,isOpenhandsCloud:n,isCustomHost:r,hasApiKey:i,source:o})=>{a(`backend_added`,{backend_kind:e,connection_method:t,is_openhands_cloud:n,is_custom_host:r,has_api_key:i,source:o})},trackOnboardingStarted:()=>{a(`onboarding_started`)},trackOnboardingStepViewed:({step:e,stepIndex:t,totalSteps:n,agent:r})=>{a(`onboarding_step_viewed`,{step:e,step_index:t,total_steps:n,agent:r})},trackOnboardingCompleted:({agent:e})=>{a(`onboarding_completed`,{agent:e})},trackOnboardingSkipped:({step:e,stepIndex:t,totalSteps:n,agent:r})=>{a(`onboarding_skipped`,{step:e,step_index:t,total_steps:n,agent:r})}}};exports.useTracking=n;
|
|
2
2
|
//# sourceMappingURL=use-tracking.cjs.map
|