@runeya/runeya 2.0.45 → 2.0.46
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/agent/index.js +5 -5
- package/agent/index.js.map +1 -1
- package/agent-manager-JJYZDLNB.js +7 -0
- package/{chunk-B6C5AXMK.js → chunk-5PRQBES2.js} +247 -63
- package/chunk-5PRQBES2.js.map +1 -0
- package/index.js +1 -1
- package/package.json +1 -1
- package/{src-ZKQETCHR.js → src-5QPLS5TQ.js} +1102 -493
- package/src-5QPLS5TQ.js.map +1 -0
- package/web/assets/{AgentScalarChatInterface.vue-IDhv2vYG.js → AgentScalarChatInterface.vue-Z3BLbola.js} +1 -1
- package/web/assets/AgentTerminalPage-BlQ3k7VX.css +1 -0
- package/web/assets/AgentTerminalPage-Dg4kOMbK.js +1 -0
- package/web/assets/AiChatPage-0igbt0Ie.js +1 -0
- package/web/assets/AppLayout-BQpUnN2N.js +1 -0
- package/web/assets/{AppLayout-Bqs7ug1K.css → AppLayout-D1bdzemo.css} +1 -1
- package/web/assets/{AuditPage-D__LOvkN.js → AuditPage-zfim5OLa.js} +1 -1
- package/web/assets/{CloudAccountPage-ll1uAGyN.js → CloudAccountPage-BD-L27Wl.js} +1 -1
- package/web/assets/CloudAccountPage-CHZG-vv-.js +1 -0
- package/web/assets/CloudMessagingPage-BnaGxzNK.js +2 -0
- package/web/assets/CloudMessagingPage-x4bDmgyb.css +1 -0
- package/web/assets/{DashboardPage-XFl45skz.js → DashboardPage-ByzcpJV7.js} +1 -1
- package/web/assets/{EditorIcon-DHxnxi15.js → EditorIcon-jk0Nz8Mr.js} +1 -1
- package/web/assets/{EnvironmentForm-D_JXws5q.js → EnvironmentForm-BH4EMxM6.js} +1 -1
- package/web/assets/{EnvironmentFormPage-7YhkQZnF.js → EnvironmentFormPage-Bwjr8SsV.js} +1 -1
- package/web/assets/EnvironmentListPage-BgqHe-4p.js +1 -0
- package/web/assets/EnvironmentListPage-BwgJscFQ.css +1 -0
- package/web/assets/{EnvironmentSelector-zX2RSq7p.js → EnvironmentSelector-CBh21uuY.js} +1 -1
- package/web/assets/{KanbanBoardPage-CYiVnEsW.js → KanbanBoardPage-Dg0Od24D.js} +3 -3
- package/web/assets/{KanbanListPage-DA8VLR_A.js → KanbanListPage-C6bsD-gJ.js} +1 -1
- package/web/assets/LaunchProjectPage-BZe2kaRt.js +1 -0
- package/web/assets/LaunchProjectPage-Bqm88kwd.css +1 -0
- package/web/assets/LaunchServicesPage-B4xd4APa.js +1 -0
- package/web/assets/LaunchServicesPage-pWREsZTD.css +1 -0
- package/web/assets/{LogViewer-DfbwNIUv.js → LogViewer-BhaSYgvw.js} +1 -1
- package/web/assets/{ProcessControls-CZV4iizX.js → ProcessControls-lDF1qNIM.js} +1 -1
- package/web/assets/{ProcessDetailPage-CeatPGNs.js → ProcessDetailPage-DwaApuHs.js} +1 -1
- package/web/assets/{ProcessMetrics-C5U4PvOh.js → ProcessMetrics-rGmADRtS.js} +1 -1
- package/web/assets/ProjectDetailPage-BoIcnBMH.js +1 -0
- package/web/assets/ProjectDetailPage-hcU-rKhJ.css +1 -0
- package/web/assets/ProjectListPage-C3JtBL-6.js +1 -0
- package/web/assets/ProjectListPage-DYs6X7r2.css +1 -0
- package/web/assets/{RandomJobTestPage-ycTTIaQd.js → RandomJobTestPage-CndKGKYR.js} +1 -1
- package/web/assets/{ScenarioEditorPage-C1EV-XSW.js → ScenarioEditorPage-DU7tb4M-.js} +1 -1
- package/web/assets/{SendToChatDialog-28Mlo-hb.js → SendToChatDialog-CCRCitBB.js} +1 -1
- package/web/assets/{ServiceControlsBar-CdWFcbXZ.js → ServiceControlsBar-BjyMYoiN.js} +1 -1
- package/web/assets/{ServiceDetailPage-BvoD3SB6.css → ServiceDetailPage-BRg1byDZ.css} +1 -1
- package/web/assets/ServiceDetailPage-JqpmujmY.js +2 -0
- package/web/assets/ServiceFormPage-B8jcMT3b.css +1 -0
- package/web/assets/ServiceFormPage-DgNwiYzM.js +1 -0
- package/web/assets/{SettingsPage-B1jaxJGj.js → SettingsPage-BZM3WWqc.js} +4 -4
- package/web/assets/{UnlockPage-ktTtBOr2.js → UnlockPage-BcZ2vL57.js} +1 -1
- package/web/assets/{VariablesTable-B9Nhdk1_.js → VariablesTable-0Gz2ffiL.js} +1 -1
- package/web/assets/{VaultMigrationPage-Dy48s3x-.js → VaultMigrationPage-BxTBDL19.js} +1 -1
- package/web/assets/{VersionBadge-BdlVr8Tg.js → VersionBadge-DvsaGqvh.js} +1 -1
- package/web/assets/WikiPage-DkEQXkzp.js +1 -0
- package/web/assets/{WikiView-CJDAiZr5.js → WikiView-e81ejcA8.js} +1 -1
- package/web/assets/{add-scalar-classes-pA8osZfz.js → add-scalar-classes-Bh8ghfnQ.js} +3 -3
- package/web/assets/auth-errors-C1B7h06f.js +1 -0
- package/web/assets/{autocomplete-N91is6wh.js → autocomplete-CDWkaHAi.js} +1 -1
- package/web/assets/{basic-shape-diamond.svg-BllfU_oB.js → basic-shape-diamond.svg-7zqPzGW0.js} +1 -1
- package/web/assets/{basic-shape-hexagon.svg-DW5bh6IP.js → basic-shape-hexagon.svg-DQwxqMd2.js} +1 -1
- package/web/assets/{basic-shape-primary-circle-ellipse-round.svg-9uuUiv5o.js → basic-shape-primary-circle-ellipse-round.svg-Bqm796vK.js} +1 -1
- package/web/assets/{basic-shape-primary-square-rectangle.svg-B64Eo9nB.js → basic-shape-primary-square-rectangle.svg-D7eyO04i.js} +1 -1
- package/web/assets/{basic-shape-shield.svg-DOPLFilS.js → basic-shape-shield.svg-Bnc_pFVA.js} +1 -1
- package/web/assets/{browser-SZtn0i0X.js → browser-DeXg6zUY.js} +1 -1
- package/web/assets/{browser-DueDd93t.js → browser-LxxhMX-Z.js} +1 -1
- package/web/assets/{card-COABLB3K.js → card-RaXQSIfv.js} +1 -1
- package/web/assets/{checkbox-B_GT2gG5.js → checkbox-DnpUXs7B.js} +1 -1
- package/web/assets/{chip--NXSwDoR.js → chip-0f0r_-zR.js} +1 -1
- package/web/assets/{client-BhFbvoQd.js → client-CTzGkOjO.js} +1 -1
- package/web/assets/cloud-chat.store-D2oJ_DFn.js +1 -0
- package/web/assets/{cloud-session-BSsRB6P3.js → cloud-session-BLn7b3ke.js} +1 -1
- package/web/assets/{column-BtS8Diqq.js → column-DqAehgUU.js} +1 -1
- package/web/assets/{computer-device-desktop-monitor.svg-BzjC7P3F.js → computer-device-desktop-monitor.svg-k2x9Oiiu.js} +1 -1
- package/web/assets/{computer-device-desktop.svg-BecVq71S.js → computer-device-desktop.svg-CyFljvrh.js} +1 -1
- package/web/assets/{computer-device-laptop.svg-Dme_bl6m.js → computer-device-laptop.svg-ZKpV0gtW.js} +1 -1
- package/web/assets/{computer-device-mobile-phone-android-samsung-back.svg-DCxv5fub.js → computer-device-mobile-phone-android-samsung-back.svg-BkRkRpCh.js} +1 -1
- package/web/assets/{computer-device-mobile-phone-android-samsung.svg--sSW4sfn.js → computer-device-mobile-phone-android-samsung.svg-gpUXFo9R.js} +1 -1
- package/web/assets/{computer-device-mobile-phone-iphone-x-back.svg-h5Po-TDX.js → computer-device-mobile-phone-iphone-x-back.svg-CJwr0WJ3.js} +1 -1
- package/web/assets/{computer-device-mobile-phone-iphone-x.svg-C2Uy1WjT.js → computer-device-mobile-phone-iphone-x.svg-BsPF_njN.js} +1 -1
- package/web/assets/{computer-device-mobile-tablet-touch.svg-CaeEG4WK.js → computer-device-mobile-tablet-touch.svg-DON7qRSP.js} +1 -1
- package/web/assets/{computer-device-mobile-tablet.svg-BpXku7YF.js → computer-device-mobile-tablet.svg-BPnT3MvS.js} +1 -1
- package/web/assets/{computer-device-network-ethernet-cat6.svg-aNAbOQCS.js → computer-device-network-ethernet-cat6.svg-Bpjv_2ub.js} +1 -1
- package/web/assets/{computer-device-network-lan-www.svg-ClT8Xavc.js → computer-device-network-lan-www.svg-BhXBIyrf.js} +1 -1
- package/web/assets/{computer-device-network-wifi-connection.svg-BBVSSCLm.js → computer-device-network-wifi-connection.svg-DUaA1GcU.js} +1 -1
- package/web/assets/{computer-device-network-wifi-router.svg-DHEuRciK.js → computer-device-network-wifi-router.svg-k_DqQho1.js} +1 -1
- package/web/assets/{cssMode-X03N01aE.js → cssMode-CfVTS8OM.js} +1 -1
- package/web/assets/{divider-B8p6MBJX.js → divider-C1huit3M.js} +1 -1
- package/web/assets/{ecology-science-erlenmeyer-flask.svg-2iSCfC0H.js → ecology-science-erlenmeyer-flask.svg-D7pbgLTT.js} +1 -1
- package/web/assets/{fieldset-B9fGvXQ_.js → fieldset-BVRjgmg4.js} +1 -1
- package/web/assets/{freemarker2-BR_8OWIK.js → freemarker2-DsY_gjrP.js} +1 -1
- package/web/assets/{handlebars-B3tZa--p.js → handlebars-mh1HrGq7.js} +1 -1
- package/web/assets/{html-BZBK1HMw.js → html-BsaAhzOa.js} +1 -1
- package/web/assets/{htmlMode-BVt6cDn5.js → htmlMode-pIhI3cAi.js} +1 -1
- package/web/assets/{image-flash-lightning.svg-CGclepmP.js → image-flash-lightning.svg-DDbhd-eu.js} +1 -1
- package/web/assets/{image-picture-flower.svg-Cl3xtcRn.js → image-picture-flower.svg-Bbx1pu02.js} +1 -1
- package/web/assets/{index-BqCmf99K.css → index-D7XqcLhE.css} +1 -1
- package/web/assets/{index-BPW4FgHw.js → index-Do0zA8cN.js} +215 -215
- package/web/assets/{interface-alert-exclamation-diamond.svg-CmooSrfz.js → interface-alert-exclamation-diamond.svg-DUJeN1hs.js} +1 -1
- package/web/assets/{interface-alert-exclamation-triangle-warning.svg-CkbjudIG.js → interface-alert-exclamation-triangle-warning.svg-5y99yZSY.js} +1 -1
- package/web/assets/{interface-alert-information-circle.svg-BVOJniJB.js → interface-alert-information-circle.svg-DPGP01ZU.js} +1 -1
- package/web/assets/{interface-award-crown.svg-BtCDhizx.js → interface-award-crown.svg-GDQ8ZRUh.js} +1 -1
- package/web/assets/{interface-bookmark.svg-CI3C_CEm.js → interface-bookmark-tag.svg-D1NUMLB5.js} +1 -1
- package/web/assets/{interface-bookmark-tag.svg-DpZpJCxG.js → interface-bookmark.svg-CTZlXD4R.js} +1 -1
- package/web/assets/{interface-calendar-date-one.svg-DeSRwc9I.js → interface-calendar-date-one.svg-DXo4raY8.js} +1 -1
- package/web/assets/{interface-content-book-open-pages.svg-CsYHQiGg.js → interface-content-book-open-pages.svg-DTsCVw_D.js} +1 -1
- package/web/assets/{interface-content-book-page.svg-DusdhNh8.js → interface-content-book-page.svg-w9azHLUe.js} +1 -1
- package/web/assets/{interface-content-file.svg-BZpRl2vg.js → interface-content-file.svg-69CCnbNb.js} +1 -1
- package/web/assets/{interface-content-folder.svg-BHyjskUa.js → interface-content-folder.svg-85PnJNaK.js} +1 -1
- package/web/assets/interface-content-folder.svg-Db3hJMdF.js +1 -0
- package/web/assets/{interface-copy-clipboard.svg-CU8KQ56k.js → interface-copy-clipboard.svg-B0AfVljN.js} +1 -1
- package/web/assets/{interface-edit-attachment.svg-sLGtjIVg.js → interface-edit-attachment.svg-mFLLqqcw.js} +1 -1
- package/web/assets/{interface-edit-binocular.svg-Kr0btLKm.js → interface-edit-binocular.svg-CHzgny3z.js} +1 -1
- package/web/assets/{interface-edit-magic-wand.svg-DuZj2AYt.js → interface-edit-magic-wand.svg-BbJg9LMT.js} +1 -1
- package/web/assets/{interface-edit-tool-paint-roller.svg-Da6-5RAS.js → interface-edit-tool-paint-roller.svg-YKE28Xpc.js} +1 -1
- package/web/assets/{interface-edit-tool-pencil.svg-BoPw8ms_.js → interface-edit-tool-pencil.svg-Cy12MfEi.js} +1 -1
- package/web/assets/{interface-favorite-award.svg-C3ITWbq3.js → interface-favorite-award.svg-COiLJu4w.js} +1 -1
- package/web/assets/{interface-favorite-flag.svg-GCr6RXH0.js → interface-favorite-flag.svg-C0NtaPD_.js} +1 -1
- package/web/assets/{interface-favorite-heart.svg-BY9dqVB0.js → interface-favorite-heart.svg-CcKJOjOh.js} +1 -1
- package/web/assets/{interface-favorite-star.svg-uA4scusZ.js → interface-favorite-star.svg-C3vRaRtM.js} +1 -1
- package/web/assets/{interface-favorite-stars-sparkles.svg-BdSCoHYl.js → interface-favorite-stars-sparkles.svg-DN9WOkFs.js} +1 -1
- package/web/assets/{interface-hierarchy-flowchart.svg-CxNFchvq.js → interface-hierarchy-flowchart.svg-CffgUWNS.js} +1 -1
- package/web/assets/{interface-home-house.svg-DkrKouBb.js → interface-home-house.svg-B9c4N9Fi.js} +1 -1
- package/web/assets/{interface-hyperlink.svg-I-GxkQJY.js → interface-hyperlink.svg-B8dXB276.js} +1 -1
- package/web/assets/{interface-lighting-brightness.svg-BJR3bOQ-.js → interface-lighting-brightness.svg-BjFVAOW3.js} +1 -1
- package/web/assets/{interface-lock-closed.svg-C0b_bVUd.js → interface-lock-closed.svg-DwNcKmwu.js} +1 -1
- package/web/assets/{interface-lock-open-unlock.svg-BtmiNLaj.js → interface-lock-open-unlock.svg-BXZeKlHt.js} +1 -1
- package/web/assets/{interface-login-key.svg-DmYxn7RL.js → interface-login-key.svg-CQnZGVMJ.js} +1 -1
- package/web/assets/{interface-search.svg-C1pVTe7C.js → interface-search.svg-CXre9Ja4.js} +1 -1
- package/web/assets/{interface-setting-cog.svg-C42e2OTW.js → interface-setting-cog.svg-CGK2QxqV.js} +1 -1
- package/web/assets/{interface-share-megaphone-bullhorn.svg-o9tOlEh2.js → interface-share-megaphone-bullhorn.svg-BcdTvuin.js} +1 -1
- package/web/assets/{interface-share-rocket.svg-Da9sYj_t.js → interface-share-rocket.svg-DIvKBIQX.js} +1 -1
- package/web/assets/{interface-share-satellite.svg-B20KpyfR.js → interface-share-satellite.svg-DNbqAorz.js} +1 -1
- package/web/assets/{interface-share-space-ship.svg-B16T8uJ6.js → interface-share-space-ship.svg-Biobo33q.js} +1 -1
- package/web/assets/{interface-share.svg-Bwk_5ajK.js → interface-share.svg-DWjt5rBb.js} +1 -1
- package/web/assets/{interface-signal-square.svg-CAVSymjP.js → interface-signal-square.svg--hmv5CmE.js} +1 -1
- package/web/assets/{interface-time-clock-circle.svg-fjss8ru1.js → interface-time-clock-circle.svg-C1GnMIQD.js} +1 -1
- package/web/assets/{interface-time-hour-glass.svg-DaTX8FMo.js → interface-time-hour-glass.svg-BCRsHIbm.js} +1 -1
- package/web/assets/{interface-users-multiple.svg-D-EFFOw7.js → interface-users-multiple.svg-CUpeUldF.js} +1 -1
- package/web/assets/{interface-weather-moon.svg-CrrnEbU7.js → interface-weather-moon.svg-QXMY6B27.js} +1 -1
- package/web/assets/{javascript-C6MNjbYD.js → javascript-DKeyayKI.js} +1 -1
- package/web/assets/{jsonMode-CA2Tf-2T.js → jsonMode-I9zemAn2.js} +1 -1
- package/web/assets/{liquid-DBKs6KRv.js → liquid-xEdzGxP1.js} +1 -1
- package/web/assets/{lspLanguageFeatures-BPpii0dk.js → lspLanguageFeatures-DOVjCuiu.js} +1 -1
- package/web/assets/{mail-chat-bubble-square.svg-lR62-873.js → mail-chat-bubble-square.svg-CR8byO78.js} +1 -1
- package/web/assets/{mail-send-email-paper-airplane.svg-Bkg6W9qr.js → mail-send-email-paper-airplane.svg-B0cIMUKH.js} +1 -1
- package/web/assets/{mail-send-envelope.svg-CbNm7aaE.js → mail-send-envelope.svg-El4e7bXy.js} +1 -1
- package/web/assets/{mdx-nUeiRsUz.js → mdx-AWoH37r-.js} +1 -1
- package/web/assets/{message-D6qRK6Jo.js → message-CATQqbP_.js} +1 -1
- package/web/assets/{minus-DoeouXPt.js → minus-Sd1wO9DO.js} +1 -1
- package/web/assets/modal-BbDElcmR.js +1 -0
- package/web/assets/{modal-kvCAAvat.js → modal-ZXFHQ6vP.js} +1 -1
- package/web/assets/{money-cashier-receipt.svg-DV9XlLpx.js → money-cashier-receipt.svg-BIEGQvlX.js} +1 -1
- package/web/assets/{money-currency-dollar-pay.svg-_jLRI9-z.js → money-currency-dollar-pay.svg-BgCHAYq5.js} +1 -1
- package/web/assets/{money-graph-arrow-increase.svg-Tnvl7X21.js → money-graph-arrow-increase.svg-q6aDu3og.js} +1 -1
- package/web/assets/{money-graph-bar-chart-increase.svg-uo4bmQha.js → money-graph-bar-chart-increase.svg-mRvG_dWg.js} +1 -1
- package/web/assets/{multiselect-BJUl25Yx.js → multiselect-BK7hJt7W.js} +1 -1
- package/web/assets/{nature-ecology-leaf.svg-CFuPp8bO.js → nature-ecology-leaf.svg-DdhvC8mq.js} +1 -1
- package/web/assets/{password-Cmgp-gwd.js → password-Dnf9oU5a.js} +1 -1
- package/web/assets/{phone-telephone.svg-CPKz1YHt.js → phone-telephone.svg-CjbyxKZA.js} +1 -1
- package/web/assets/{plus-s_OJLoyY.js → plus-jYeRYaw9.js} +1 -1
- package/web/assets/process.store-C5LXrPXo.js +1 -0
- package/web/assets/{programming-bug.svg-fxsVnaeH.js → programming-bug.svg-DvDCWw4P.js} +1 -1
- package/web/assets/{programming-cloud.svg-BnU51aLl.js → programming-cloud.svg-2prWAqB7.js} +1 -1
- package/web/assets/{programming-computer-database-server.svg-BZgDGKJW.js → programming-computer-database-server.svg-CT7k5d_p.js} +1 -1
- package/web/assets/{programming-computer-database.svg-B5S5rAvT.js → programming-computer-database.svg-Dm7fpvII.js} +1 -1
- package/web/assets/{programming-module-four-layout.svg-QRzEO0n6.js → programming-module-four-layout.svg-DBv6QvEO.js} +1 -1
- package/web/assets/{programming-module-three.svg-CM_P7Vlm.js → programming-module-three.svg-BnP96bPJ.js} +1 -1
- package/web/assets/{programming-module.svg-3GTFIIEX.js → programming-module.svg-Bdr4ZdZO.js} +1 -1
- package/web/assets/{programming-script-code.svg-KtOz6fyB.js → programming-script-code.svg-DIcr6sF-.js} +1 -1
- package/web/assets/{python-Dm07kugX.js → python-C554d-pk.js} +1 -1
- package/web/assets/{razor-bhq-c8Pc.js → razor-BvOiTjBp.js} +1 -1
- package/web/assets/router-CCEIu6U5.js +1 -0
- package/web/assets/{settings.store-DkR2x1Gd.js → settings.store-DISlY-48.js} +1 -1
- package/web/assets/{shopping-cart.svg-z5f-zUgD.js → shopping-cart.svg-DoCVSOin.js} +1 -1
- package/web/assets/{shopping-gift-present.svg-BuM8QoB9.js → shopping-gift-present.svg-CgNqOSQr.js} +1 -1
- package/web/assets/{shopping-shipping-box-parcel-package.svg-CWw8lTQj.js → shopping-shipping-box-parcel-package.svg-YkVPEp7J.js} +1 -1
- package/web/assets/{src-BNgzvl34.js → src-Cvt5pswj.js} +1 -1
- package/web/assets/{src-G9u1k3Z0.js → src-DKVQlKvZ.js} +1 -1
- package/web/assets/{tabpanel-C_MAwvD8.js → tabpanel-DFoLVdFR.js} +1 -1
- package/web/assets/{tag-new-circle.svg-C2PwVxge.js → tag-new-circle.svg-BeUe12X2.js} +1 -1
- package/web/assets/{travel-map-earth-globe.svg-Cva7Lt5l.js → travel-map-earth-globe.svg-B1I1Ed-v.js} +1 -1
- package/web/assets/trpc-CVPkysoa.js +1 -0
- package/web/assets/{tsMode-CiozJEFc.js → tsMode-BPD-0Vep.js} +1 -1
- package/web/assets/{typescript-DjFjYcp9.js → typescript-Q2uKnndS.js} +1 -1
- package/web/assets/useAiChatDrawer-B70XYuU8.js +1 -0
- package/web/assets/useCloudOrg-CZ68epfW.js +1 -0
- package/web/assets/useProjectCatalog-Kh_sHbSD.js +1 -0
- package/web/assets/{xml-_A4d3R4e.js → xml-B0CfAsFk.js} +1 -1
- package/web/assets/{yaml-B2BOTmvS.js → yaml-B3zRI6AS.js} +1 -1
- package/web/index.html +2 -2
- package/agent-manager-RQEJLSSF.js +0 -7
- package/chunk-B6C5AXMK.js.map +0 -1
- package/src-ZKQETCHR.js.map +0 -1
- package/web/assets/AgentTerminalPage-BNUuSKXa.js +0 -1
- package/web/assets/AgentTerminalPage-CQxk6jVs.css +0 -1
- package/web/assets/AiChatPage-i_HxoGlU.js +0 -1
- package/web/assets/AppLayout-BtVQtKnv.js +0 -1
- package/web/assets/CloudAccountPage-CEf5yWA2.js +0 -1
- package/web/assets/CloudMessagingPage-B63PMN88.js +0 -2
- package/web/assets/CloudMessagingPage-DbhpPX4q.css +0 -1
- package/web/assets/EnvironmentListPage-BDqcug66.css +0 -1
- package/web/assets/EnvironmentListPage-DyNlU_ys.js +0 -1
- package/web/assets/LaunchProjectPage-B7ch8hBp.js +0 -1
- package/web/assets/LaunchProjectPage-CxXC2U3O.css +0 -1
- package/web/assets/LaunchServicesPage-BAvD32G1.css +0 -1
- package/web/assets/LaunchServicesPage-BxbnthGj.js +0 -1
- package/web/assets/ProjectDetailPage-B42imx75.css +0 -1
- package/web/assets/ProjectDetailPage-BaZIvewr.js +0 -1
- package/web/assets/ProjectListPage-BGU6SKAr.css +0 -1
- package/web/assets/ProjectListPage-BreaFS_h.js +0 -1
- package/web/assets/ServiceDetailPage-DAq3ofhT.js +0 -2
- package/web/assets/ServiceFormPage-BER91b-6.js +0 -1
- package/web/assets/ServiceFormPage-D31sr_k0.css +0 -1
- package/web/assets/WikiPage-CMqO84ak.js +0 -1
- package/web/assets/auth-errors-B3tAiztt.js +0 -1
- package/web/assets/cloud-chat.store-DEKUnKhm.js +0 -1
- package/web/assets/interface-content-folder.svg-CE1AjWC2.js +0 -1
- package/web/assets/modal-OTFsRnDn.js +0 -1
- package/web/assets/process.store-dKQhHReg.js +0 -1
- package/web/assets/router-BamtimoJ.js +0 -1
- package/web/assets/trpc-BdB_KPws.js +0 -1
- package/web/assets/useAiChatDrawer-DpUd_4Sa.js +0 -1
- package/web/assets/useCloudOrg-DcDNmhwp.js +0 -1
- /package/{agent-manager-RQEJLSSF.js.map → agent-manager-JJYZDLNB.js.map} +0 -0
- /package/web/assets/{abap-lJFTLuI6.js → abap-DMmvWZUy.js} +0 -0
- /package/web/assets/{apex-Bp0KWav8.js → apex-D89PiTzU.js} +0 -0
- /package/web/assets/{azcli-DBOe7Uvy.js → azcli-ChnjTTA5.js} +0 -0
- /package/web/assets/{bat-Dk_NT_by.js → bat-uLzQ_FhK.js} +0 -0
- /package/web/assets/{bicep-B07Mrlc7.js → bicep-CMOK7Q1v.js} +0 -0
- /package/web/assets/{cameligo-DL5eNXGn.js → cameligo-D1AyojFH.js} +0 -0
- /package/web/assets/{clojure-BzV2Yqu_.js → clojure-D9JoUgXn.js} +0 -0
- /package/web/assets/{coffee-Bjlj5hmf.js → coffee-D-5sSToL.js} +0 -0
- /package/web/assets/{cpp-C9oth5qz.js → cpp-BqT_t_y9.js} +0 -0
- /package/web/assets/{csharp-HyGd049S.js → csharp-CuToYI91.js} +0 -0
- /package/web/assets/{csp-DjCDOBuX.js → csp-CffcxvlO.js} +0 -0
- /package/web/assets/{css-DIL8RTJ3.js → css-CUD0biQW.js} +0 -0
- /package/web/assets/{cypher-DX1zPXL5.js → cypher-ByyWL_9U.js} +0 -0
- /package/web/assets/{dart-BBQZd_ZZ.js → dart-B__ipcUn.js} +0 -0
- /package/web/assets/{dockerfile-Cj0CaWYx.js → dockerfile-DS-_8bWw.js} +0 -0
- /package/web/assets/{ecl-BjmWCTJg.js → ecl-DWsNCBUj.js} +0 -0
- /package/web/assets/{elixir-3uWoEjXX.js → elixir-OfjEy-3g.js} +0 -0
- /package/web/assets/{flow9-D4r739e9.js → flow9-BPX22hxS.js} +0 -0
- /package/web/assets/{fsharp-B3ycAx5T.js → fsharp-CVJcfIUn.js} +0 -0
- /package/web/assets/{go-DFHBjfCl.js → go-CfRHZ3yg.js} +0 -0
- /package/web/assets/{graphql-DBjIbM_e.js → graphql-BZaO3ttK.js} +0 -0
- /package/web/assets/{hcl-Buu_1Hsp.js → hcl-BF9-qUku.js} +0 -0
- /package/web/assets/{ini-CgzZhgz_.js → ini-BUvw6xhr.js} +0 -0
- /package/web/assets/{java-BczDO0q5.js → java-BZ5BNd2x.js} +0 -0
- /package/web/assets/{julia-B142kn-7.js → julia-DN38cgYT.js} +0 -0
- /package/web/assets/{kotlin-42XMP7p1.js → kotlin-CjEJKT8q.js} +0 -0
- /package/web/assets/{less-BCIsKRqU.js → less-DMEH_Nwq.js} +0 -0
- /package/web/assets/{lexon-DTDOH1dQ.js → lexon-CKQUX8Ov.js} +0 -0
- /package/web/assets/{lua-BrH-CeNs.js → lua-CuWjKLJ0.js} +0 -0
- /package/web/assets/{m3-D-eUeHOI.js → m3-CEp1bVrM.js} +0 -0
- /package/web/assets/{markdown-DwpSjZTn.js → markdown-DxtQ_puq.js} +0 -0
- /package/web/assets/{mips-DoGTrjVk.js → mips-Dx7qydpO.js} +0 -0
- /package/web/assets/{msdax-CQ8McnjQ.js → msdax-DpNCaSiO.js} +0 -0
- /package/web/assets/{mysql-Cj3do0ZO.js → mysql-C3xsBDPP.js} +0 -0
- /package/web/assets/{objective-c-BZbR8TG8.js → objective-c-BbaVHCX1.js} +0 -0
- /package/web/assets/{pascal-23PRDHRq.js → pascal-sDju2faq.js} +0 -0
- /package/web/assets/{pascaligo-CxasKTLJ.js → pascaligo-ChCdOH6b.js} +0 -0
- /package/web/assets/{perl-Dhxz2Lhx.js → perl-BCgLFj01.js} +0 -0
- /package/web/assets/{pgsql-BIbUy7yD.js → pgsql-DtKby69f.js} +0 -0
- /package/web/assets/{php-B5qS9qgM.js → php-NYvR17Qb.js} +0 -0
- /package/web/assets/{pla-Bolz9fha.js → pla-gSg-RBG0.js} +0 -0
- /package/web/assets/{postiats-Ba6dE5lj.js → postiats-BQxHyDfy.js} +0 -0
- /package/web/assets/{powerquery-2jW3ONgo.js → powerquery-DzDmbVZN.js} +0 -0
- /package/web/assets/{powershell-CMfBIiJp.js → powershell-yy4Q5BEQ.js} +0 -0
- /package/web/assets/{protobuf-BpaquOUj.js → protobuf-B_hIk9q0.js} +0 -0
- /package/web/assets/{pug-cBG82Ydz.js → pug-CdeEb1ft.js} +0 -0
- /package/web/assets/{qsharp-DtqL8Lmt.js → qsharp-CiSquEgT.js} +0 -0
- /package/web/assets/{r-CKLew4sj.js → r-D1Ocz1x4.js} +0 -0
- /package/web/assets/{redis-BdDxRU-N.js → redis-D42Y12yz.js} +0 -0
- /package/web/assets/{redshift-X4a4MlqW.js → redshift-CS62MYfG.js} +0 -0
- /package/web/assets/{restructuredtext-LA4Fv05h.js → restructuredtext-CKFSMEXw.js} +0 -0
- /package/web/assets/{ruby-CibE_WVM.js → ruby-C4gTaeU3.js} +0 -0
- /package/web/assets/{rust-BK6RsiL0.js → rust-C_f2AiHQ.js} +0 -0
- /package/web/assets/{sb-BnCCt0Qj.js → sb-CdNdpP1r.js} +0 -0
- /package/web/assets/{scala-oaBDrVqj.js → scala-N4EWaFTL.js} +0 -0
- /package/web/assets/{scheme-C-wkLC01.js → scheme-CttIARxx.js} +0 -0
- /package/web/assets/{scss-BceVGOJY.js → scss-v0wtbxaE.js} +0 -0
- /package/web/assets/{shell-CxhBAFA5.js → shell-BJdEWXbc.js} +0 -0
- /package/web/assets/{solidity-D9sXgTfx.js → solidity-DKDa59zZ.js} +0 -0
- /package/web/assets/{sophia-C4VBhIEM.js → sophia--Kw-vDpr.js} +0 -0
- /package/web/assets/{sparql-DbsK2s9y.js → sparql-BI8jS9Pc.js} +0 -0
- /package/web/assets/{sql-CuGfE0wH.js → sql-Dz6z--6O.js} +0 -0
- /package/web/assets/{st-rB6Bqb2h.js → st-CT8D05gv.js} +0 -0
- /package/web/assets/{swift-BtHGe_4T.js → swift-Ayb3KzWx.js} +0 -0
- /package/web/assets/{systemverilog-CgKnzwU4.js → systemverilog-BtWJM9O5.js} +0 -0
- /package/web/assets/{tcl-DyqQWf87.js → tcl-BJvUWpbV.js} +0 -0
- /package/web/assets/{twig-C980XO5P.js → twig-c3YaBCU6.js} +0 -0
- /package/web/assets/{typespec-CVmA80px.js → typespec-gFCWLBoZ.js} +0 -0
- /package/web/assets/{uuid-2Kb5PDpq.js → uuid-B3B8HIK9.js} +0 -0
- /package/web/assets/{vb-rdNQv5VQ.js → vb-CVtfWXV2.js} +0 -0
- /package/web/assets/{w3c-keyname-B2B-WOb1.js → w3c-keyname-nn0G3QC2.js} +0 -0
- /package/web/assets/{wgsl-64oVbimL.js → wgsl-C_huaGQy.js} +0 -0
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
AGENT_LONG_FETCH_TIMEOUT,
|
|
3
3
|
AGENT_STOP_FETCH_TIMEOUT,
|
|
4
4
|
AIProviderSchema,
|
|
5
|
+
AmbiguousProjectError,
|
|
5
6
|
ApiKeyScopeSchema,
|
|
6
7
|
ApiKeysFileSchema,
|
|
7
8
|
CreateEnvironmentSchema,
|
|
@@ -42,6 +43,7 @@ import {
|
|
|
42
43
|
encryptVariableSlots,
|
|
43
44
|
ensureEncryptionKey,
|
|
44
45
|
ensureLocalVault,
|
|
46
|
+
ensureProjectsLoaded,
|
|
45
47
|
env,
|
|
46
48
|
environmentStore,
|
|
47
49
|
extractEncryptedVariableSlots,
|
|
@@ -64,10 +66,13 @@ import {
|
|
|
64
66
|
machineRoot,
|
|
65
67
|
nativeParsers,
|
|
66
68
|
orgVaultRoot,
|
|
69
|
+
projectKey,
|
|
67
70
|
projectVaultRoot,
|
|
68
71
|
readEntity,
|
|
72
|
+
readOrgClouds,
|
|
73
|
+
refOfProjectAmong,
|
|
69
74
|
registerProjectLoader,
|
|
70
|
-
|
|
75
|
+
rootOfProjectAmong,
|
|
71
76
|
serviceStore,
|
|
72
77
|
setEncryptionKey,
|
|
73
78
|
setEncryptionKeyInMemory,
|
|
@@ -75,16 +80,17 @@ import {
|
|
|
75
80
|
setProjectServices,
|
|
76
81
|
setProjectVault,
|
|
77
82
|
stableEncryptValue,
|
|
83
|
+
stampOrgCloud,
|
|
78
84
|
traefikManager,
|
|
79
85
|
vaultFor,
|
|
80
86
|
writeEntity
|
|
81
|
-
} from "./chunk-
|
|
87
|
+
} from "./chunk-5PRQBES2.js";
|
|
82
88
|
|
|
83
89
|
// ../server/src/create-server.ts
|
|
84
90
|
import express from "express";
|
|
85
91
|
import cors from "cors";
|
|
86
92
|
import { createServer } from "http";
|
|
87
|
-
import { resolve as
|
|
93
|
+
import { resolve as resolve6 } from "path";
|
|
88
94
|
import { WebSocketServer } from "ws";
|
|
89
95
|
import { createExpressMiddleware } from "@trpc/server/adapters/express";
|
|
90
96
|
import { applyWSSHandler } from "@trpc/server/adapters/ws";
|
|
@@ -545,6 +551,12 @@ var LocalProjectExistsError = class extends Error {
|
|
|
545
551
|
code = "LOCAL_PROJECT_EXISTS";
|
|
546
552
|
};
|
|
547
553
|
var ProjectStore = class {
|
|
554
|
+
/**
|
|
555
|
+
* Keyed by the (organization, project) couple, never by the project id alone:
|
|
556
|
+
* the same id can name a different project in each organization, and keying
|
|
557
|
+
* by id let the second one read from disk overwrite the first — then `save()`
|
|
558
|
+
* wrote that survivor back into both vaults.
|
|
559
|
+
*/
|
|
548
560
|
projects = /* @__PURE__ */ new Map();
|
|
549
561
|
loaded = false;
|
|
550
562
|
/** Emits `'saved'` after every persisted mutation, for the cloud sync bridge. */
|
|
@@ -562,15 +574,15 @@ var ProjectStore = class {
|
|
|
562
574
|
async absorb(p, root) {
|
|
563
575
|
const projectEnvs = await environmentStore.getByProjectId(p.id).catch(() => []);
|
|
564
576
|
const defaultEnv = projectEnvs.find((e) => e.default);
|
|
565
|
-
this.projects.set(p
|
|
577
|
+
this.projects.set(projectKey(p), {
|
|
566
578
|
...p,
|
|
567
579
|
environmentIds: p.environmentIds ?? [],
|
|
568
580
|
groupOrder: p.groupOrder ?? [],
|
|
569
581
|
// Prefer the env marked default, fall back to first env
|
|
570
582
|
activeEnvironmentId: defaultEnv?.id ?? p.environmentIds?.[0]
|
|
571
583
|
});
|
|
572
|
-
setProjectVault(p
|
|
573
|
-
setProjectServices(p
|
|
584
|
+
setProjectVault(p, root);
|
|
585
|
+
setProjectServices(p, p.serviceIds ?? []);
|
|
574
586
|
}
|
|
575
587
|
/**
|
|
576
588
|
* Read every vault: the launch directory, then each organization's projects.
|
|
@@ -636,17 +648,40 @@ var ProjectStore = class {
|
|
|
636
648
|
for (const { project, root } of cloud) {
|
|
637
649
|
await mkdir2(root, { recursive: true });
|
|
638
650
|
await this.writeAtomic(this.getFilePath(root), JSON.stringify(toDiskForm(project), null, 2));
|
|
639
|
-
setProjectVault(project
|
|
651
|
+
setProjectVault(project, root);
|
|
640
652
|
}
|
|
641
653
|
this.events.emit("saved");
|
|
642
654
|
}
|
|
655
|
+
/**
|
|
656
|
+
* The project a caller means, from a couple or from a bare id.
|
|
657
|
+
*
|
|
658
|
+
* A ref carrying its `orgId` — even an explicit `null` for a local project —
|
|
659
|
+
* names exactly one project and is looked up directly. A bare id is what a
|
|
660
|
+
* contained entity (an environment, a conversation) still carries, and it is
|
|
661
|
+
* resolved only while it answers for a single project: electing one of two
|
|
662
|
+
* would write into the wrong organization's vault.
|
|
663
|
+
*/
|
|
664
|
+
resolveKey(target) {
|
|
665
|
+
if (typeof target !== "string" && target.orgId !== void 0) return projectKey(target);
|
|
666
|
+
const id = typeof target === "string" ? target : target.id;
|
|
667
|
+
const matches = Array.from(this.projects.values()).filter((p) => p.id === id);
|
|
668
|
+
if (matches.length > 1) {
|
|
669
|
+
throw new AmbiguousProjectError(id, matches.map((p) => p.orgId ?? null));
|
|
670
|
+
}
|
|
671
|
+
return matches[0] ? projectKey(matches[0]) : null;
|
|
672
|
+
}
|
|
673
|
+
find(target) {
|
|
674
|
+
const key = this.resolveKey(target);
|
|
675
|
+
const project = key ? this.projects.get(key) : void 0;
|
|
676
|
+
return key && project ? { key, project } : null;
|
|
677
|
+
}
|
|
643
678
|
async list() {
|
|
644
679
|
await this.load();
|
|
645
680
|
return Array.from(this.projects.values());
|
|
646
681
|
}
|
|
647
|
-
async get(
|
|
682
|
+
async get(target) {
|
|
648
683
|
await this.load();
|
|
649
|
-
return this.
|
|
684
|
+
return this.find(target)?.project ?? null;
|
|
650
685
|
}
|
|
651
686
|
async create(input) {
|
|
652
687
|
await this.load();
|
|
@@ -666,16 +701,22 @@ var ProjectStore = class {
|
|
|
666
701
|
activeEnvironmentId: input.activeEnvironmentId,
|
|
667
702
|
groupOrder: input.groupOrder ?? []
|
|
668
703
|
};
|
|
669
|
-
this.projects.set(project
|
|
670
|
-
setProjectVault(project
|
|
671
|
-
setProjectServices(project
|
|
704
|
+
this.projects.set(projectKey(project), project);
|
|
705
|
+
setProjectVault(project, vaultFor(project).root);
|
|
706
|
+
setProjectServices(project, project.serviceIds);
|
|
672
707
|
await this.save();
|
|
673
708
|
return project;
|
|
674
709
|
}
|
|
710
|
+
/**
|
|
711
|
+
* `input.orgId` names which project is being updated, it does not move it:
|
|
712
|
+
* a project is born local or in an organization and stays there, so treating
|
|
713
|
+
* it as a new value would silently relocate a vault instead of editing it.
|
|
714
|
+
*/
|
|
675
715
|
async update(input) {
|
|
676
716
|
await this.load();
|
|
677
|
-
const
|
|
678
|
-
if (!
|
|
717
|
+
const found = this.find(input);
|
|
718
|
+
if (!found) return null;
|
|
719
|
+
const { key, project: existing } = found;
|
|
679
720
|
const updated = {
|
|
680
721
|
...existing,
|
|
681
722
|
...input.name !== void 0 && { name: input.name },
|
|
@@ -683,20 +724,20 @@ var ProjectStore = class {
|
|
|
683
724
|
...input.serviceIds !== void 0 && { serviceIds: input.serviceIds },
|
|
684
725
|
...input.environmentIds !== void 0 && { environmentIds: input.environmentIds },
|
|
685
726
|
...input.activeEnvironmentId !== void 0 && { activeEnvironmentId: input.activeEnvironmentId },
|
|
686
|
-
...input.orgId !== void 0 && { orgId: input.orgId },
|
|
687
727
|
...input.groupOrder !== void 0 && { groupOrder: input.groupOrder }
|
|
688
728
|
};
|
|
689
|
-
this.projects.set(
|
|
690
|
-
setProjectVault(updated
|
|
691
|
-
setProjectServices(updated
|
|
729
|
+
this.projects.set(key, updated);
|
|
730
|
+
setProjectVault(updated, vaultFor(updated).root);
|
|
731
|
+
setProjectServices(updated, updated.serviceIds);
|
|
692
732
|
await this.save();
|
|
693
733
|
return updated;
|
|
694
734
|
}
|
|
695
|
-
async delete(
|
|
735
|
+
async delete(target) {
|
|
696
736
|
await this.load();
|
|
697
|
-
const
|
|
698
|
-
if (!
|
|
699
|
-
|
|
737
|
+
const found = this.find(target);
|
|
738
|
+
if (!found) return false;
|
|
739
|
+
const { key, project } = found;
|
|
740
|
+
this.projects.delete(key);
|
|
700
741
|
await this.save();
|
|
701
742
|
await this.discardVault(project);
|
|
702
743
|
return true;
|
|
@@ -710,7 +751,7 @@ var ProjectStore = class {
|
|
|
710
751
|
* are only removed from the list.
|
|
711
752
|
*/
|
|
712
753
|
async discardVault(project) {
|
|
713
|
-
forgetProject(project
|
|
754
|
+
forgetProject(project);
|
|
714
755
|
if (!project.orgId) return;
|
|
715
756
|
await rm(vaultFor(project).root, { recursive: true, force: true }).catch(() => {
|
|
716
757
|
});
|
|
@@ -723,14 +764,15 @@ var ProjectStore = class {
|
|
|
723
764
|
* back on the next save — and, its project being unknown by then, into the
|
|
724
765
|
* local vault.
|
|
725
766
|
*/
|
|
726
|
-
async forgetLocally(
|
|
767
|
+
async forgetLocally(target) {
|
|
727
768
|
await this.load();
|
|
728
|
-
const
|
|
729
|
-
if (!
|
|
730
|
-
|
|
731
|
-
|
|
769
|
+
const found = this.find(target);
|
|
770
|
+
if (!found) return;
|
|
771
|
+
const { key, project } = found;
|
|
772
|
+
this.projects.delete(key);
|
|
773
|
+
forgetProject(project);
|
|
732
774
|
await serviceStore.forgetLocally(project.serviceIds ?? []);
|
|
733
|
-
await environmentStore.forgetByProject(
|
|
775
|
+
await environmentStore.forgetByProject(project.id);
|
|
734
776
|
}
|
|
735
777
|
/** All projects linked to a given organization (for the sync bridge). */
|
|
736
778
|
async listByOrg(orgId) {
|
|
@@ -759,92 +801,99 @@ var ProjectStore = class {
|
|
|
759
801
|
return null;
|
|
760
802
|
}
|
|
761
803
|
const incoming = toDiskForm(parsed.data);
|
|
762
|
-
const
|
|
804
|
+
const key = projectKey(parsed.data);
|
|
805
|
+
const existing = this.projects.get(key);
|
|
763
806
|
if (existing) {
|
|
764
807
|
if (JSON.stringify(toDiskForm(existing)) === JSON.stringify(incoming)) {
|
|
765
808
|
return { project: existing, created: false };
|
|
766
809
|
}
|
|
767
810
|
const updated = { ...incoming, activeEnvironmentId: existing.activeEnvironmentId };
|
|
768
|
-
this.projects.set(
|
|
769
|
-
setProjectVault(
|
|
770
|
-
setProjectServices(
|
|
811
|
+
this.projects.set(key, updated);
|
|
812
|
+
setProjectVault(updated, vaultFor(updated).root);
|
|
813
|
+
setProjectServices(updated, updated.serviceIds);
|
|
771
814
|
await this.save();
|
|
772
815
|
return { project: updated, created: false };
|
|
773
816
|
}
|
|
774
817
|
const project = { ...incoming, activeEnvironmentId: void 0 };
|
|
775
|
-
this.projects.set(
|
|
776
|
-
setProjectVault(
|
|
777
|
-
setProjectServices(
|
|
818
|
+
this.projects.set(key, project);
|
|
819
|
+
setProjectVault(project, vaultFor(project).root);
|
|
820
|
+
setProjectServices(project, project.serviceIds);
|
|
778
821
|
await this.save();
|
|
779
822
|
return { project, created: true };
|
|
780
823
|
}
|
|
781
824
|
/** Delete a project that was removed on another peer. */
|
|
782
|
-
async applyRemoteDelete(
|
|
825
|
+
async applyRemoteDelete(target) {
|
|
783
826
|
await this.load();
|
|
784
|
-
const
|
|
785
|
-
if (!
|
|
786
|
-
|
|
827
|
+
const found = this.find(target);
|
|
828
|
+
if (!found) return false;
|
|
829
|
+
const { key, project } = found;
|
|
830
|
+
this.projects.delete(key);
|
|
787
831
|
await this.save();
|
|
788
832
|
await this.discardVault(project);
|
|
789
833
|
return true;
|
|
790
834
|
}
|
|
791
|
-
async addService(
|
|
835
|
+
async addService(target, serviceId) {
|
|
792
836
|
await this.load();
|
|
793
|
-
const
|
|
794
|
-
if (!
|
|
837
|
+
const found = this.find(target);
|
|
838
|
+
if (!found) return null;
|
|
839
|
+
const { key, project } = found;
|
|
795
840
|
if (project.serviceIds.includes(serviceId)) return project;
|
|
796
841
|
const updated = { ...project, serviceIds: [...project.serviceIds, serviceId] };
|
|
797
|
-
this.projects.set(
|
|
798
|
-
setProjectServices(
|
|
842
|
+
this.projects.set(key, updated);
|
|
843
|
+
setProjectServices(updated, updated.serviceIds);
|
|
799
844
|
await this.save();
|
|
800
845
|
return updated;
|
|
801
846
|
}
|
|
802
|
-
async removeService(
|
|
847
|
+
async removeService(target, serviceId) {
|
|
803
848
|
await this.load();
|
|
804
|
-
const
|
|
805
|
-
if (!
|
|
849
|
+
const found = this.find(target);
|
|
850
|
+
if (!found) return null;
|
|
851
|
+
const { key, project } = found;
|
|
806
852
|
const updated = { ...project, serviceIds: project.serviceIds.filter((id) => id !== serviceId) };
|
|
807
|
-
this.projects.set(
|
|
808
|
-
setProjectServices(
|
|
853
|
+
this.projects.set(key, updated);
|
|
854
|
+
setProjectServices(updated, updated.serviceIds);
|
|
809
855
|
await this.save();
|
|
810
856
|
return updated;
|
|
811
857
|
}
|
|
812
|
-
async addEnvironment(
|
|
858
|
+
async addEnvironment(target, environmentId) {
|
|
813
859
|
await this.load();
|
|
814
|
-
const
|
|
815
|
-
if (!
|
|
860
|
+
const found = this.find(target);
|
|
861
|
+
if (!found) return null;
|
|
862
|
+
const { key, project } = found;
|
|
816
863
|
if (project.environmentIds?.includes(environmentId)) return project;
|
|
817
864
|
const updated = {
|
|
818
865
|
...project,
|
|
819
866
|
environmentIds: [...project.environmentIds ?? [], environmentId]
|
|
820
867
|
};
|
|
821
|
-
this.projects.set(
|
|
868
|
+
this.projects.set(key, updated);
|
|
822
869
|
await this.save();
|
|
823
870
|
return updated;
|
|
824
871
|
}
|
|
825
|
-
async removeEnvironment(
|
|
872
|
+
async removeEnvironment(target, environmentId) {
|
|
826
873
|
await this.load();
|
|
827
|
-
const
|
|
828
|
-
if (!
|
|
874
|
+
const found = this.find(target);
|
|
875
|
+
if (!found) return null;
|
|
876
|
+
const { key, project } = found;
|
|
829
877
|
const updated = {
|
|
830
878
|
...project,
|
|
831
879
|
environmentIds: (project.environmentIds ?? []).filter((id) => id !== environmentId),
|
|
832
880
|
// Clear activeEnvironmentId if it was the removed environment
|
|
833
881
|
...project.activeEnvironmentId === environmentId && { activeEnvironmentId: void 0 }
|
|
834
882
|
};
|
|
835
|
-
this.projects.set(
|
|
883
|
+
this.projects.set(key, updated);
|
|
836
884
|
await this.save();
|
|
837
885
|
return updated;
|
|
838
886
|
}
|
|
839
|
-
async setActiveEnvironment(
|
|
887
|
+
async setActiveEnvironment(target, environmentId) {
|
|
840
888
|
await this.load();
|
|
841
|
-
const
|
|
842
|
-
if (!
|
|
889
|
+
const found = this.find(target);
|
|
890
|
+
if (!found) return null;
|
|
891
|
+
const { key, project } = found;
|
|
843
892
|
const updated = {
|
|
844
893
|
...project,
|
|
845
894
|
activeEnvironmentId: environmentId
|
|
846
895
|
};
|
|
847
|
-
this.projects.set(
|
|
896
|
+
this.projects.set(key, updated);
|
|
848
897
|
return updated;
|
|
849
898
|
}
|
|
850
899
|
};
|
|
@@ -876,8 +925,8 @@ var EnvironmentOverridesStore = class {
|
|
|
876
925
|
* may simply not be loaded yet.
|
|
877
926
|
*/
|
|
878
927
|
async rootFor(variableId) {
|
|
879
|
-
const
|
|
880
|
-
if (
|
|
928
|
+
const root = await environmentStore.rootOfVariable(variableId);
|
|
929
|
+
if (root) return root;
|
|
881
930
|
return this.roots.get(variableId) ?? dirname2(await keyedFilePath(FILENAME2));
|
|
882
931
|
}
|
|
883
932
|
/** Where earlier versions kept them: read once, rewritten to their vaults. */
|
|
@@ -894,6 +943,7 @@ var EnvironmentOverridesStore = class {
|
|
|
894
943
|
if (this.loaded) return;
|
|
895
944
|
if (!getEncryptionKey()) return;
|
|
896
945
|
this.loaded = true;
|
|
946
|
+
await ensureProjectsLoaded();
|
|
897
947
|
const roots = await allVaultRoots();
|
|
898
948
|
const sources = [
|
|
899
949
|
this.getLegacyFilePath(),
|
|
@@ -1459,13 +1509,98 @@ var AuditService = class {
|
|
|
1459
1509
|
};
|
|
1460
1510
|
var auditService = new AuditService();
|
|
1461
1511
|
|
|
1512
|
+
// ../server/src/services/project-path-store.ts
|
|
1513
|
+
import { readFile as readFile5, writeFile as writeFile5, rename as rename5, mkdir as mkdir5, chmod as chmod4, stat as stat2 } from "fs/promises";
|
|
1514
|
+
import { isAbsolute, join as join4, resolve } from "path";
|
|
1515
|
+
var FILENAME3 = "project-paths.json";
|
|
1516
|
+
var ProjectPathStore = class {
|
|
1517
|
+
paths = null;
|
|
1518
|
+
async load() {
|
|
1519
|
+
if (this.paths) return this.paths;
|
|
1520
|
+
try {
|
|
1521
|
+
const raw = await readFile5(machineFilePath(FILENAME3), "utf-8");
|
|
1522
|
+
const parsed = JSON.parse(raw);
|
|
1523
|
+
this.paths = isPathMap(parsed) ? parsed : {};
|
|
1524
|
+
} catch {
|
|
1525
|
+
this.paths = {};
|
|
1526
|
+
}
|
|
1527
|
+
return this.paths;
|
|
1528
|
+
}
|
|
1529
|
+
async persist() {
|
|
1530
|
+
await mkdir5(machineRoot(), { recursive: true });
|
|
1531
|
+
const filePath = machineFilePath(FILENAME3);
|
|
1532
|
+
const tmpPath = `${filePath}.tmp`;
|
|
1533
|
+
await writeFile5(tmpPath, JSON.stringify(this.paths ?? {}, null, 2), "utf-8");
|
|
1534
|
+
await rename5(tmpPath, filePath);
|
|
1535
|
+
await chmod4(filePath, 384);
|
|
1536
|
+
}
|
|
1537
|
+
/** The directory chosen for this project, or null while it has none. */
|
|
1538
|
+
async get(ref) {
|
|
1539
|
+
const paths = await this.load();
|
|
1540
|
+
return paths[projectKey(ref)] ?? paths[ref.id] ?? null;
|
|
1541
|
+
}
|
|
1542
|
+
async all() {
|
|
1543
|
+
return { ...await this.load() };
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* The project already placed in this directory, if any other one is.
|
|
1547
|
+
*
|
|
1548
|
+
* Answers with the key as stored — the couple, or the bare id for an entry an
|
|
1549
|
+
* older version wrote — which is enough to say "taken" and to name it.
|
|
1550
|
+
*/
|
|
1551
|
+
async projectAt(path, exclude) {
|
|
1552
|
+
const target = resolve(path);
|
|
1553
|
+
const excluded = exclude ? [projectKey(exclude), exclude.id] : [];
|
|
1554
|
+
for (const [key, dir] of Object.entries(await this.load())) {
|
|
1555
|
+
if (!excluded.includes(key) && resolve(dir) === target) return key;
|
|
1556
|
+
}
|
|
1557
|
+
return null;
|
|
1558
|
+
}
|
|
1559
|
+
async set(ref, path) {
|
|
1560
|
+
if (!isAbsolute(path)) throw new Error("Le chemin du projet doit \xEAtre absolu");
|
|
1561
|
+
const paths = await this.load();
|
|
1562
|
+
delete paths[ref.id];
|
|
1563
|
+
paths[projectKey(ref)] = resolve(path);
|
|
1564
|
+
await this.persist();
|
|
1565
|
+
}
|
|
1566
|
+
async clear(ref) {
|
|
1567
|
+
const paths = await this.load();
|
|
1568
|
+
const keys = [projectKey(ref), ref.id].filter((k) => k in paths);
|
|
1569
|
+
if (keys.length === 0) return;
|
|
1570
|
+
for (const key of keys) delete paths[key];
|
|
1571
|
+
await this.persist();
|
|
1572
|
+
}
|
|
1573
|
+
/** Test seam: forget what was read from disk. */
|
|
1574
|
+
reset() {
|
|
1575
|
+
this.paths = null;
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
function isPathMap(value) {
|
|
1579
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((v) => typeof v === "string");
|
|
1580
|
+
}
|
|
1581
|
+
async function holdsRuneyaProject(path) {
|
|
1582
|
+
for (const candidate of ["project.json", "projects.json"]) {
|
|
1583
|
+
try {
|
|
1584
|
+
await stat2(join4(path, ".runeya", candidate));
|
|
1585
|
+
return true;
|
|
1586
|
+
} catch {
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
return false;
|
|
1590
|
+
}
|
|
1591
|
+
var projectPathStore = new ProjectPathStore();
|
|
1592
|
+
|
|
1462
1593
|
// ../server/src/trpc/routers/project.ts
|
|
1463
1594
|
var projectRouter = router({
|
|
1464
1595
|
list: protectedProcedure.query(async () => {
|
|
1465
1596
|
return projectStore.list();
|
|
1466
1597
|
}),
|
|
1467
|
-
|
|
1468
|
-
|
|
1598
|
+
/**
|
|
1599
|
+
* `orgId` names which project — the same id can exist in several
|
|
1600
|
+
* organizations. Left out, the id must answer for exactly one project.
|
|
1601
|
+
*/
|
|
1602
|
+
get: protectedProcedure.input(z2.object({ id: z2.string().max(128), orgId: z2.string().max(128).nullable().optional() })).query(async ({ input }) => {
|
|
1603
|
+
const project = await projectStore.get(input);
|
|
1469
1604
|
if (!project) {
|
|
1470
1605
|
throw new TRPCError2({ code: "NOT_FOUND", message: `Project ${input.id} not found` });
|
|
1471
1606
|
}
|
|
@@ -1477,8 +1612,8 @@ var projectRouter = router({
|
|
|
1477
1612
|
* Environments are intentionally excluded — they carry secret values that
|
|
1478
1613
|
* stay local.
|
|
1479
1614
|
*/
|
|
1480
|
-
export: protectedProcedure.input(z2.object({ id: z2.string().max(128) })).query(async ({ input }) => {
|
|
1481
|
-
const project = await projectStore.get(input
|
|
1615
|
+
export: protectedProcedure.input(z2.object({ id: z2.string().max(128), orgId: z2.string().max(128).nullable().optional() })).query(async ({ input }) => {
|
|
1616
|
+
const project = await projectStore.get(input);
|
|
1482
1617
|
if (!project) {
|
|
1483
1618
|
throw new TRPCError2({ code: "NOT_FOUND", message: `Project ${input.id} not found` });
|
|
1484
1619
|
}
|
|
@@ -1513,9 +1648,10 @@ var projectRouter = router({
|
|
|
1513
1648
|
description: "Default local environment (immutable)",
|
|
1514
1649
|
parentIds: [],
|
|
1515
1650
|
immutable: true
|
|
1516
|
-
});
|
|
1651
|
+
}, project.orgId);
|
|
1517
1652
|
const result = await projectStore.update({
|
|
1518
1653
|
id: project.id,
|
|
1654
|
+
orgId: project.orgId,
|
|
1519
1655
|
environmentIds: [localEnv.id],
|
|
1520
1656
|
activeEnvironmentId: localEnv.id
|
|
1521
1657
|
});
|
|
@@ -1539,22 +1675,27 @@ var projectRouter = router({
|
|
|
1539
1675
|
await auditService.log({ userId: "local", action: "update", entityType: "project", entityId: input.id });
|
|
1540
1676
|
return project;
|
|
1541
1677
|
}),
|
|
1542
|
-
delete: protectedProcedure.input(z2.object({ id: z2.string().max(128) })).mutation(async ({ input }) => {
|
|
1543
|
-
const deleted = await projectStore.delete(input
|
|
1678
|
+
delete: protectedProcedure.input(z2.object({ id: z2.string().max(128), orgId: z2.string().max(128).nullable().optional() })).mutation(async ({ input }) => {
|
|
1679
|
+
const deleted = await projectStore.delete(input);
|
|
1544
1680
|
if (!deleted) {
|
|
1545
1681
|
throw new TRPCError2({ code: "NOT_FOUND", message: `Project ${input.id} not found` });
|
|
1546
1682
|
}
|
|
1547
1683
|
await auditService.log({ userId: "local", action: "delete", entityType: "project", entityId: input.id });
|
|
1548
1684
|
return { success: true };
|
|
1549
1685
|
}),
|
|
1550
|
-
addService: protectedProcedure.input(z2.object({
|
|
1551
|
-
|
|
1686
|
+
addService: protectedProcedure.input(z2.object({
|
|
1687
|
+
projectId: z2.string().max(128),
|
|
1688
|
+
orgId: z2.string().max(128).nullable().optional(),
|
|
1689
|
+
serviceId: z2.string().max(128)
|
|
1690
|
+
})).mutation(async ({ input }) => {
|
|
1691
|
+
const target = { id: input.projectId, orgId: input.orgId };
|
|
1692
|
+
const project = await projectStore.addService(target, input.serviceId);
|
|
1552
1693
|
if (!project) {
|
|
1553
1694
|
throw new TRPCError2({ code: "NOT_FOUND", message: `Project ${input.projectId} not found` });
|
|
1554
1695
|
}
|
|
1555
1696
|
const service = await serviceStore.get(input.serviceId);
|
|
1556
1697
|
if (service) {
|
|
1557
|
-
const globalEnvs = await environmentStore.listByScope(input.projectId, "global");
|
|
1698
|
+
const globalEnvs = await environmentStore.listByScope(input.projectId, "global", input.orgId);
|
|
1558
1699
|
for (const globalEnv of globalEnvs) {
|
|
1559
1700
|
const svcEnv = await environmentStore.create({
|
|
1560
1701
|
projectId: input.projectId,
|
|
@@ -1564,8 +1705,8 @@ var projectRouter = router({
|
|
|
1564
1705
|
activateOn: globalEnv.id,
|
|
1565
1706
|
parentIds: [globalEnv.id],
|
|
1566
1707
|
variables: {}
|
|
1567
|
-
});
|
|
1568
|
-
await projectStore.addEnvironment(
|
|
1708
|
+
}, input.orgId);
|
|
1709
|
+
await projectStore.addEnvironment(target, svcEnv.id);
|
|
1569
1710
|
}
|
|
1570
1711
|
}
|
|
1571
1712
|
await auditService.log({
|
|
@@ -1577,8 +1718,15 @@ var projectRouter = router({
|
|
|
1577
1718
|
});
|
|
1578
1719
|
return project;
|
|
1579
1720
|
}),
|
|
1580
|
-
removeService: protectedProcedure.input(z2.object({
|
|
1581
|
-
|
|
1721
|
+
removeService: protectedProcedure.input(z2.object({
|
|
1722
|
+
projectId: z2.string().max(128),
|
|
1723
|
+
orgId: z2.string().max(128).nullable().optional(),
|
|
1724
|
+
serviceId: z2.string().max(128)
|
|
1725
|
+
})).mutation(async ({ input }) => {
|
|
1726
|
+
const project = await projectStore.removeService(
|
|
1727
|
+
{ id: input.projectId, orgId: input.orgId },
|
|
1728
|
+
input.serviceId
|
|
1729
|
+
);
|
|
1582
1730
|
if (!project) {
|
|
1583
1731
|
throw new TRPCError2({ code: "NOT_FOUND", message: `Project ${input.projectId} not found` });
|
|
1584
1732
|
}
|
|
@@ -1591,8 +1739,12 @@ var projectRouter = router({
|
|
|
1591
1739
|
});
|
|
1592
1740
|
return project;
|
|
1593
1741
|
}),
|
|
1594
|
-
setActiveEnvironment: protectedProcedure.input(z2.object({
|
|
1595
|
-
|
|
1742
|
+
setActiveEnvironment: protectedProcedure.input(z2.object({
|
|
1743
|
+
projectId: z2.string().max(128),
|
|
1744
|
+
orgId: z2.string().max(128).nullable().optional(),
|
|
1745
|
+
environmentId: z2.string().max(128)
|
|
1746
|
+
})).mutation(async ({ input }) => {
|
|
1747
|
+
const project = await projectStore.get({ id: input.projectId, orgId: input.orgId });
|
|
1596
1748
|
if (!project) {
|
|
1597
1749
|
throw new TRPCError2({ code: "NOT_FOUND", message: `Project ${input.projectId} not found` });
|
|
1598
1750
|
}
|
|
@@ -1621,7 +1773,10 @@ var projectRouter = router({
|
|
|
1621
1773
|
} catch {
|
|
1622
1774
|
}
|
|
1623
1775
|
}
|
|
1624
|
-
const updated = await projectStore.setActiveEnvironment(
|
|
1776
|
+
const updated = await projectStore.setActiveEnvironment(
|
|
1777
|
+
{ id: input.projectId, orgId: input.orgId },
|
|
1778
|
+
input.environmentId
|
|
1779
|
+
);
|
|
1625
1780
|
if (!updated) {
|
|
1626
1781
|
throw new TRPCError2({ code: "INTERNAL_SERVER_ERROR", message: "Failed to set active environment" });
|
|
1627
1782
|
}
|
|
@@ -1656,6 +1811,53 @@ var projectRouter = router({
|
|
|
1656
1811
|
}
|
|
1657
1812
|
}
|
|
1658
1813
|
return updated;
|
|
1814
|
+
}),
|
|
1815
|
+
/**
|
|
1816
|
+
* Where this project lives on this machine, for every project at once.
|
|
1817
|
+
*
|
|
1818
|
+
* Machine-local by design: a directory of mine means nothing on a teammate's
|
|
1819
|
+
* machine, and the cloud sync pushes projects wholesale — a path stored on the
|
|
1820
|
+
* project itself would travel with it.
|
|
1821
|
+
*/
|
|
1822
|
+
localPaths: protectedProcedure.query(async () => {
|
|
1823
|
+
return projectPathStore.all();
|
|
1824
|
+
}),
|
|
1825
|
+
getLocalPath: protectedProcedure.input(z2.object({
|
|
1826
|
+
id: z2.string().min(1).max(128),
|
|
1827
|
+
orgId: z2.string().max(128).nullable().optional()
|
|
1828
|
+
})).query(async ({ input }) => {
|
|
1829
|
+
return { path: await projectPathStore.get(input) };
|
|
1830
|
+
}),
|
|
1831
|
+
/**
|
|
1832
|
+
* Place a project in a directory of this machine.
|
|
1833
|
+
*
|
|
1834
|
+
* Refuses a directory that already holds a Runeya project, or one another
|
|
1835
|
+
* project was already placed in: two projects sharing a directory is what
|
|
1836
|
+
* makes services run against the wrong code.
|
|
1837
|
+
*/
|
|
1838
|
+
setLocalPath: protectedProcedure.input(z2.object({
|
|
1839
|
+
id: z2.string().min(1).max(128),
|
|
1840
|
+
orgId: z2.string().max(128).nullable().optional(),
|
|
1841
|
+
path: z2.string().min(1).max(4096)
|
|
1842
|
+
})).mutation(async ({ input }) => {
|
|
1843
|
+
const project = await projectStore.get(input);
|
|
1844
|
+
if (!project) throw new TRPCError2({ code: "NOT_FOUND", message: "Projet introuvable" });
|
|
1845
|
+
if (await holdsRuneyaProject(input.path)) {
|
|
1846
|
+
throw new TRPCError2({
|
|
1847
|
+
code: "CONFLICT",
|
|
1848
|
+
message: "Ce r\xE9pertoire contient d\xE9j\xE0 un projet Runeya"
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1851
|
+
const taken = await projectPathStore.projectAt(input.path, project);
|
|
1852
|
+
if (taken) {
|
|
1853
|
+
throw new TRPCError2({
|
|
1854
|
+
code: "CONFLICT",
|
|
1855
|
+
message: "Un autre projet est d\xE9j\xE0 rattach\xE9 \xE0 ce r\xE9pertoire"
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
await projectPathStore.set(project, input.path);
|
|
1859
|
+
await auditService.log("local", "update", "project", input.id, { localPath: input.path });
|
|
1860
|
+
return { path: input.path };
|
|
1659
1861
|
})
|
|
1660
1862
|
});
|
|
1661
1863
|
|
|
@@ -1664,7 +1866,7 @@ import { z as z3 } from "zod";
|
|
|
1664
1866
|
import { TRPCError as TRPCError3 } from "@trpc/server";
|
|
1665
1867
|
|
|
1666
1868
|
// ../server/src/services/settings-manager.ts
|
|
1667
|
-
import { readFile as
|
|
1869
|
+
import { readFile as readFile6, writeFile as writeFile6, rename as rename6, mkdir as mkdir6, chmod as chmod5 } from "fs/promises";
|
|
1668
1870
|
var SETTINGS_FILE = "settings.json";
|
|
1669
1871
|
var SETTINGS_PRIVATE_FILE = "settings.private.json";
|
|
1670
1872
|
var SCHEMA_VERSION_FILE = "schema-version.json";
|
|
@@ -1674,21 +1876,21 @@ var SettingsManager = class {
|
|
|
1674
1876
|
settingsPrivate = null;
|
|
1675
1877
|
schemaVersion = null;
|
|
1676
1878
|
async ensureDir() {
|
|
1677
|
-
await
|
|
1879
|
+
await mkdir6(machineRoot(), { recursive: true });
|
|
1678
1880
|
}
|
|
1679
1881
|
async atomicWrite(filename, data) {
|
|
1680
1882
|
await this.ensureDir();
|
|
1681
1883
|
const filePath = machineFilePath(filename);
|
|
1682
1884
|
const tmpPath = filePath + ".tmp";
|
|
1683
|
-
await
|
|
1684
|
-
await
|
|
1685
|
-
await
|
|
1885
|
+
await writeFile6(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
1886
|
+
await rename6(tmpPath, filePath);
|
|
1887
|
+
await chmod5(filePath, 384);
|
|
1686
1888
|
}
|
|
1687
1889
|
/** Read the machine file, falling back to the launch directory of old. */
|
|
1688
1890
|
async readJson(filename) {
|
|
1689
1891
|
for (const path of [machineFilePath(filename), legacyMachineFilePath(filename)]) {
|
|
1690
1892
|
try {
|
|
1691
|
-
return JSON.parse(await
|
|
1893
|
+
return JSON.parse(await readFile6(path, "utf-8"));
|
|
1692
1894
|
} catch {
|
|
1693
1895
|
}
|
|
1694
1896
|
}
|
|
@@ -1751,6 +1953,62 @@ var SettingsManager = class {
|
|
|
1751
1953
|
};
|
|
1752
1954
|
var settingsManager = new SettingsManager();
|
|
1753
1955
|
|
|
1956
|
+
// ../server/src/trpc/routers/service.ts
|
|
1957
|
+
import { isAbsolute as isAbsolute3 } from "path";
|
|
1958
|
+
|
|
1959
|
+
// ../server/src/services/workspace-cwd.ts
|
|
1960
|
+
import { isAbsolute as isAbsolute2, join as join5, dirname as dirname3 } from "path";
|
|
1961
|
+
async function projectRoot(ref) {
|
|
1962
|
+
const project = await projectStore.get(ref);
|
|
1963
|
+
if (!project?.orgId) return null;
|
|
1964
|
+
return projectPathStore.get(project);
|
|
1965
|
+
}
|
|
1966
|
+
async function resolveWorkspaceCwd(target) {
|
|
1967
|
+
const launchDir = localVaultRootParent();
|
|
1968
|
+
if (target.serviceId) {
|
|
1969
|
+
const service = await serviceStore.get(target.serviceId);
|
|
1970
|
+
if (service) {
|
|
1971
|
+
const raw = (target.override ?? service.cwd ?? "").trim();
|
|
1972
|
+
const projects = await projectStore.list();
|
|
1973
|
+
const owner = projects.find((p) => p.serviceIds.includes(service.id));
|
|
1974
|
+
const root = owner ? await projectRoot(owner) : null;
|
|
1975
|
+
return root ? anchor(root, raw) : raw || ".";
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
if (target.projectId) {
|
|
1979
|
+
const root = await projectRoot({ id: target.projectId, orgId: target.orgId });
|
|
1980
|
+
const raw = (target.override ?? "").trim();
|
|
1981
|
+
return root ? anchor(root, raw) : raw || launchDir;
|
|
1982
|
+
}
|
|
1983
|
+
return anchor(launchDir, (target.override ?? "").trim());
|
|
1984
|
+
}
|
|
1985
|
+
function anchor(base, raw) {
|
|
1986
|
+
if (!raw || raw === ".") return base;
|
|
1987
|
+
if (raw.startsWith("~") || raw.startsWith("$") || isAbsolute2(raw)) return raw;
|
|
1988
|
+
return join5(base, raw);
|
|
1989
|
+
}
|
|
1990
|
+
function localVaultRootParent() {
|
|
1991
|
+
return dirname3(localVaultRoot());
|
|
1992
|
+
}
|
|
1993
|
+
async function resolveAgentCwd(target) {
|
|
1994
|
+
if (target.serviceId) {
|
|
1995
|
+
const service = await serviceStore.get(target.serviceId);
|
|
1996
|
+
if (service) {
|
|
1997
|
+
const projects = await projectStore.list();
|
|
1998
|
+
const owner = projects.find((p) => p.serviceIds.includes(service.id));
|
|
1999
|
+
const root = owner ? await projectRoot(owner) : null;
|
|
2000
|
+
if (!root) return void 0;
|
|
2001
|
+
return anchor(root, (target.override ?? service.cwd ?? "").trim());
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
if (target.projectId) {
|
|
2005
|
+
const root = await projectRoot({ id: target.projectId, orgId: target.orgId });
|
|
2006
|
+
if (!root) return void 0;
|
|
2007
|
+
return anchor(root, (target.override ?? "").trim());
|
|
2008
|
+
}
|
|
2009
|
+
return void 0;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
1754
2012
|
// ../server/src/trpc/routers/service.ts
|
|
1755
2013
|
async function resolveEffectiveAgentId(service) {
|
|
1756
2014
|
if (service.agentId) return service.agentId;
|
|
@@ -1764,6 +2022,10 @@ async function resolveServiceVariables(service) {
|
|
|
1764
2022
|
const projects = await projectStore.list();
|
|
1765
2023
|
const project = projects.find((p) => p.serviceIds.includes(service.id));
|
|
1766
2024
|
let resolvedService = service;
|
|
2025
|
+
const anchored = await resolveWorkspaceCwd({ serviceId: service.id });
|
|
2026
|
+
if (anchored !== (service.cwd ?? "").trim() && isAbsolute3(anchored)) {
|
|
2027
|
+
resolvedService = { ...resolvedService, resolvedCwd: anchored };
|
|
2028
|
+
}
|
|
1767
2029
|
try {
|
|
1768
2030
|
if (service.parserIds && service.parserIds.length > 0) {
|
|
1769
2031
|
const settings = await settingsManager.getSettings();
|
|
@@ -1975,7 +2237,9 @@ var serviceRouter = router({
|
|
|
1975
2237
|
throw new TRPCError3({ code: "BAD_REQUEST", message: "No agent configured for this service" });
|
|
1976
2238
|
}
|
|
1977
2239
|
const agentResult = await agentManager.agentQuery(effectiveAgentId, "git.checkCwd", {
|
|
1978
|
-
|
|
2240
|
+
// Anchored on the project, so the check answers about the directory the
|
|
2241
|
+
// service will actually run in.
|
|
2242
|
+
cwd: await resolveWorkspaceCwd({ serviceId: service.id }),
|
|
1979
2243
|
remote: service.git?.remote,
|
|
1980
2244
|
checkReachability: input.checkReachability
|
|
1981
2245
|
});
|
|
@@ -2010,7 +2274,9 @@ var serviceRouter = router({
|
|
|
2010
2274
|
}
|
|
2011
2275
|
await agentManager.agentMutation(effectiveAgentId, "git.clone", {
|
|
2012
2276
|
remote: service.git.remote,
|
|
2013
|
-
|
|
2277
|
+
// Clones WRITE: landing this in the launch directory instead of the
|
|
2278
|
+
// project's is how a repository ends up in the wrong place.
|
|
2279
|
+
cwd: await resolveWorkspaceCwd({ serviceId: service.id })
|
|
2014
2280
|
}, 5 * 60 * 1e3);
|
|
2015
2281
|
return { success: true };
|
|
2016
2282
|
})
|
|
@@ -2144,13 +2410,13 @@ var agentRouter = router({
|
|
|
2144
2410
|
yield { agentId: agent.config.id, status: agent.status };
|
|
2145
2411
|
}
|
|
2146
2412
|
const queue = [];
|
|
2147
|
-
let
|
|
2413
|
+
let resolve7 = null;
|
|
2148
2414
|
const handler = (event) => {
|
|
2149
2415
|
if (queue.length >= SUBSCRIPTION_QUEUE_MAX) queue.shift();
|
|
2150
2416
|
queue.push(event);
|
|
2151
|
-
if (
|
|
2152
|
-
|
|
2153
|
-
|
|
2417
|
+
if (resolve7) {
|
|
2418
|
+
resolve7();
|
|
2419
|
+
resolve7 = null;
|
|
2154
2420
|
}
|
|
2155
2421
|
};
|
|
2156
2422
|
agentManager.on("status", handler);
|
|
@@ -2160,7 +2426,7 @@ var agentRouter = router({
|
|
|
2160
2426
|
yield queue.shift();
|
|
2161
2427
|
} else {
|
|
2162
2428
|
await new Promise((r) => {
|
|
2163
|
-
|
|
2429
|
+
resolve7 = r;
|
|
2164
2430
|
});
|
|
2165
2431
|
}
|
|
2166
2432
|
}
|
|
@@ -2176,14 +2442,14 @@ import { TRPCError as TRPCError5 } from "@trpc/server";
|
|
|
2176
2442
|
var SUBSCRIPTION_QUEUE_MAX2 = LOG_BUFFER_SIZE;
|
|
2177
2443
|
async function* forwardEvents(eventName, filter) {
|
|
2178
2444
|
const queue = [];
|
|
2179
|
-
let
|
|
2445
|
+
let resolve7 = null;
|
|
2180
2446
|
const handler = (data) => {
|
|
2181
2447
|
if (filter && !filter(data)) return;
|
|
2182
2448
|
if (queue.length >= SUBSCRIPTION_QUEUE_MAX2) queue.shift();
|
|
2183
2449
|
queue.push(data);
|
|
2184
|
-
if (
|
|
2185
|
-
|
|
2186
|
-
|
|
2450
|
+
if (resolve7) {
|
|
2451
|
+
resolve7();
|
|
2452
|
+
resolve7 = null;
|
|
2187
2453
|
}
|
|
2188
2454
|
};
|
|
2189
2455
|
agentManager.on(eventName, handler);
|
|
@@ -2193,7 +2459,7 @@ async function* forwardEvents(eventName, filter) {
|
|
|
2193
2459
|
yield queue.shift();
|
|
2194
2460
|
} else {
|
|
2195
2461
|
await new Promise((r) => {
|
|
2196
|
-
|
|
2462
|
+
resolve7 = r;
|
|
2197
2463
|
});
|
|
2198
2464
|
}
|
|
2199
2465
|
}
|
|
@@ -2203,7 +2469,7 @@ async function* forwardEvents(eventName, filter) {
|
|
|
2203
2469
|
}
|
|
2204
2470
|
async function* forwardLogBatches(agentId, processId) {
|
|
2205
2471
|
const queue = [];
|
|
2206
|
-
let
|
|
2472
|
+
let resolve7 = null;
|
|
2207
2473
|
const handler = (data) => {
|
|
2208
2474
|
if (data.agentId !== agentId) return;
|
|
2209
2475
|
const batch = data.batch;
|
|
@@ -2212,9 +2478,9 @@ async function* forwardLogBatches(agentId, processId) {
|
|
|
2212
2478
|
if (filtered.length === 0) return;
|
|
2213
2479
|
if (queue.length >= SUBSCRIPTION_QUEUE_MAX2) queue.shift();
|
|
2214
2480
|
queue.push(filtered);
|
|
2215
|
-
if (
|
|
2216
|
-
|
|
2217
|
-
|
|
2481
|
+
if (resolve7) {
|
|
2482
|
+
resolve7();
|
|
2483
|
+
resolve7 = null;
|
|
2218
2484
|
}
|
|
2219
2485
|
};
|
|
2220
2486
|
agentManager.on("process:log", handler);
|
|
@@ -2224,7 +2490,7 @@ async function* forwardLogBatches(agentId, processId) {
|
|
|
2224
2490
|
yield queue.shift();
|
|
2225
2491
|
} else {
|
|
2226
2492
|
await new Promise((r) => {
|
|
2227
|
-
|
|
2493
|
+
resolve7 = r;
|
|
2228
2494
|
});
|
|
2229
2495
|
}
|
|
2230
2496
|
}
|
|
@@ -2405,7 +2671,14 @@ var processRouter = router({
|
|
|
2405
2671
|
const result = await agentManager.agentMutation(
|
|
2406
2672
|
input.agentId,
|
|
2407
2673
|
"process.executeAdHocCommand",
|
|
2408
|
-
{
|
|
2674
|
+
{
|
|
2675
|
+
id: input.processId,
|
|
2676
|
+
command: input.command,
|
|
2677
|
+
// An explicit cwd wins; otherwise the service's directory, anchored on
|
|
2678
|
+
// its project rather than on the server's launch directory.
|
|
2679
|
+
cwd: input.cwd ?? await resolveAgentCwd({ serviceId: input.processId }),
|
|
2680
|
+
commandId: input.commandId
|
|
2681
|
+
},
|
|
2409
2682
|
AGENT_LONG_FETCH_TIMEOUT
|
|
2410
2683
|
);
|
|
2411
2684
|
await auditService.log({
|
|
@@ -2582,25 +2855,36 @@ var setupRouter = router({
|
|
|
2582
2855
|
import { z as z8 } from "zod";
|
|
2583
2856
|
import { TRPCError as TRPCError6 } from "@trpc/server";
|
|
2584
2857
|
var environmentRouter = router({
|
|
2585
|
-
list: protectedProcedure.input(z8.object({
|
|
2586
|
-
|
|
2858
|
+
list: protectedProcedure.input(z8.object({
|
|
2859
|
+
projectId: z8.string().max(128),
|
|
2860
|
+
orgId: z8.string().max(128).nullable().optional()
|
|
2861
|
+
})).query(async ({ input }) => {
|
|
2862
|
+
return await environmentStore.list(input.projectId, input.orgId);
|
|
2587
2863
|
}),
|
|
2588
2864
|
get: protectedProcedure.input(z8.object({ id: z8.string().max(128) })).query(async ({ input }) => {
|
|
2589
2865
|
const env2 = await environmentStore.get(input.id);
|
|
2590
2866
|
if (!env2) throw new TRPCError6({ code: "NOT_FOUND", message: `Environment ${input.id} not found` });
|
|
2591
2867
|
return env2;
|
|
2592
2868
|
}),
|
|
2593
|
-
listByScope: protectedProcedure.input(z8.object({
|
|
2594
|
-
|
|
2869
|
+
listByScope: protectedProcedure.input(z8.object({
|
|
2870
|
+
projectId: z8.string().max(128),
|
|
2871
|
+
orgId: z8.string().max(128).nullable().optional(),
|
|
2872
|
+
scope: z8.enum(["global", "service"])
|
|
2873
|
+
})).query(async ({ input }) => {
|
|
2874
|
+
return await environmentStore.listByScope(input.projectId, input.scope, input.orgId);
|
|
2595
2875
|
}),
|
|
2596
2876
|
findServiceEnvironment: protectedProcedure.input(z8.object({ serviceId: z8.string().max(128), globalEnvId: z8.string().max(128) })).query(async ({ input }) => {
|
|
2597
2877
|
return await environmentStore.findServiceEnvironment(input.serviceId, input.globalEnvId);
|
|
2598
2878
|
}),
|
|
2599
|
-
create: protectedProcedure.input(CreateEnvironmentSchema
|
|
2600
|
-
|
|
2601
|
-
|
|
2879
|
+
create: protectedProcedure.input(CreateEnvironmentSchema.extend({
|
|
2880
|
+
orgId: z8.string().max(128).nullable().optional()
|
|
2881
|
+
})).mutation(async ({ input }) => {
|
|
2882
|
+
const { orgId, ...environment } = input;
|
|
2883
|
+
const target = { id: input.projectId, orgId };
|
|
2884
|
+
const env2 = await environmentStore.create(environment, orgId);
|
|
2885
|
+
const project = await projectStore.get(target);
|
|
2602
2886
|
if (project && !project.environmentIds?.includes(env2.id)) {
|
|
2603
|
-
await projectStore.addEnvironment(
|
|
2887
|
+
await projectStore.addEnvironment(target, env2.id);
|
|
2604
2888
|
}
|
|
2605
2889
|
if (env2.scope === "global" && project) {
|
|
2606
2890
|
for (const serviceId of project.serviceIds) {
|
|
@@ -2621,8 +2905,8 @@ var environmentRouter = router({
|
|
|
2621
2905
|
activateOn: env2.id,
|
|
2622
2906
|
parentIds: [env2.id, ...parentServiceEnvIds],
|
|
2623
2907
|
variables: {}
|
|
2624
|
-
});
|
|
2625
|
-
await projectStore.addEnvironment(
|
|
2908
|
+
}, orgId);
|
|
2909
|
+
await projectStore.addEnvironment(target, svcEnv.id);
|
|
2626
2910
|
}
|
|
2627
2911
|
}
|
|
2628
2912
|
return env2;
|
|
@@ -2633,7 +2917,7 @@ var environmentRouter = router({
|
|
|
2633
2917
|
const env2 = await environmentStore.update(input);
|
|
2634
2918
|
if (!env2) throw new TRPCError6({ code: "NOT_FOUND", message: `Environment ${input.id} not found` });
|
|
2635
2919
|
if (env2.scope === "global" && input.name && input.name !== existing.name) {
|
|
2636
|
-
const project = await projectStore.get(env2.projectId);
|
|
2920
|
+
const project = await projectStore.get(environmentStore.projectRefOf(env2) ?? env2.projectId);
|
|
2637
2921
|
if (project) {
|
|
2638
2922
|
const serviceNames = /* @__PURE__ */ new Map();
|
|
2639
2923
|
for (const serviceId of project.serviceIds) {
|
|
@@ -2656,7 +2940,7 @@ var environmentRouter = router({
|
|
|
2656
2940
|
}
|
|
2657
2941
|
const deleted = await environmentStore.delete(input.id);
|
|
2658
2942
|
if (!deleted) throw new TRPCError6({ code: "INTERNAL_SERVER_ERROR", message: "Failed to delete environment" });
|
|
2659
|
-
await projectStore.removeEnvironment(env2.projectId, input.id);
|
|
2943
|
+
await projectStore.removeEnvironment(environmentStore.projectRefOf(env2) ?? env2.projectId, input.id);
|
|
2660
2944
|
return { success: true };
|
|
2661
2945
|
}),
|
|
2662
2946
|
resolve: protectedProcedure.input(z8.object({ environmentId: z8.string().max(128) })).query(async ({ input, ctx }) => {
|
|
@@ -2731,11 +3015,12 @@ var environmentRouter = router({
|
|
|
2731
3015
|
*/
|
|
2732
3016
|
keyUsages: protectedProcedure.input(z8.object({
|
|
2733
3017
|
projectId: z8.string().max(128),
|
|
3018
|
+
orgId: z8.string().max(128).nullable().optional(),
|
|
2734
3019
|
key: z8.string().min(1).max(256),
|
|
2735
3020
|
/** Environment holding the variable about to be edited — enables inherited-impact detection */
|
|
2736
3021
|
environmentId: z8.string().max(128).optional()
|
|
2737
3022
|
})).query(async ({ input }) => {
|
|
2738
|
-
const environments = await environmentStore.list(input.projectId);
|
|
3023
|
+
const environments = await environmentStore.list(input.projectId, input.orgId);
|
|
2739
3024
|
const services = await serviceStore.list();
|
|
2740
3025
|
const serviceName = (id) => services.find((s) => s.id === id)?.name;
|
|
2741
3026
|
const escaped = input.key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -2804,7 +3089,6 @@ var environmentRouter = router({
|
|
|
2804
3089
|
|
|
2805
3090
|
// ../server/src/trpc/routers/settings.ts
|
|
2806
3091
|
import { promises as dns } from "dns";
|
|
2807
|
-
import { resolve } from "path";
|
|
2808
3092
|
import { z as z9 } from "zod";
|
|
2809
3093
|
import { TRPCError as TRPCError7 } from "@trpc/server";
|
|
2810
3094
|
var NotificationPreferencesPatchSchema = NotificationPreferencesSchema.partial().strict();
|
|
@@ -2824,9 +3108,12 @@ var settingsRouter = router({
|
|
|
2824
3108
|
* Paths the server is running from (working directory + data directory).
|
|
2825
3109
|
* Read-only, purely informational for the settings UI.
|
|
2826
3110
|
*/
|
|
2827
|
-
getPaths: protectedProcedure.query(() => ({
|
|
2828
|
-
|
|
2829
|
-
|
|
3111
|
+
getPaths: protectedProcedure.query(async () => ({
|
|
3112
|
+
// The launch directory, not the process's current one — they are the same
|
|
3113
|
+
// today only because nothing calls chdir, which is exactly what must not be
|
|
3114
|
+
// relied on.
|
|
3115
|
+
cwd: await resolveWorkspaceCwd({}),
|
|
3116
|
+
dataDir: env.DATA_DIR,
|
|
2830
3117
|
// Where cloud projects live: their vaults belong to the machine, not to
|
|
2831
3118
|
// whichever directory the server happened to be started in.
|
|
2832
3119
|
machineDir: machineRoot()
|
|
@@ -3190,7 +3477,7 @@ var settingsRouter = router({
|
|
|
3190
3477
|
}
|
|
3191
3478
|
} catch {
|
|
3192
3479
|
}
|
|
3193
|
-
await new Promise((
|
|
3480
|
+
await new Promise((resolve7) => setTimeout(resolve7, 500));
|
|
3194
3481
|
}
|
|
3195
3482
|
}),
|
|
3196
3483
|
/**
|
|
@@ -3376,16 +3663,16 @@ var settingsRouter = router({
|
|
|
3376
3663
|
import { z as z11 } from "zod";
|
|
3377
3664
|
import { TRPCError as TRPCError8 } from "@trpc/server";
|
|
3378
3665
|
import { execFile as execFile2, spawn } from "child_process";
|
|
3379
|
-
import { stat as
|
|
3380
|
-
import { resolve as resolve2, join as
|
|
3666
|
+
import { stat as stat3 } from "fs/promises";
|
|
3667
|
+
import { resolve as resolve2, join as join7 } from "path";
|
|
3381
3668
|
import { homedir as homedir2 } from "os";
|
|
3382
3669
|
|
|
3383
3670
|
// ../server/src/services/user-config-manager.ts
|
|
3384
|
-
import { readFile as
|
|
3385
|
-
import { join as
|
|
3671
|
+
import { readFile as readFile7, writeFile as writeFile7, rename as rename7, mkdir as mkdir7, chmod as chmod6 } from "fs/promises";
|
|
3672
|
+
import { join as join6 } from "path";
|
|
3386
3673
|
import { homedir } from "os";
|
|
3387
3674
|
import { z as z10 } from "zod";
|
|
3388
|
-
var CONFIG_DIR =
|
|
3675
|
+
var CONFIG_DIR = join6(homedir(), ".runeya");
|
|
3389
3676
|
var CONFIG_FILE = "runeya.conf";
|
|
3390
3677
|
var EditorPreferencesSchema = z10.object({
|
|
3391
3678
|
default: z10.string().max(64).optional()
|
|
@@ -3399,23 +3686,26 @@ var UserConfigSchema = z10.record(
|
|
|
3399
3686
|
z10.string().max(128),
|
|
3400
3687
|
ProjectSettingsSchema
|
|
3401
3688
|
).refine((obj) => Object.keys(obj).length <= 100, "Too many projects in config");
|
|
3689
|
+
function configKey(ref) {
|
|
3690
|
+
return projectKey(ref);
|
|
3691
|
+
}
|
|
3402
3692
|
var UserConfigManager = class {
|
|
3403
3693
|
config = null;
|
|
3404
3694
|
async ensureDir() {
|
|
3405
|
-
await
|
|
3695
|
+
await mkdir7(CONFIG_DIR, { recursive: true });
|
|
3406
3696
|
}
|
|
3407
3697
|
async atomicWrite(data) {
|
|
3408
3698
|
await this.ensureDir();
|
|
3409
|
-
const filePath =
|
|
3699
|
+
const filePath = join6(CONFIG_DIR, CONFIG_FILE);
|
|
3410
3700
|
const tmpPath = filePath + ".tmp";
|
|
3411
|
-
await
|
|
3412
|
-
await
|
|
3413
|
-
await
|
|
3701
|
+
await writeFile7(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
3702
|
+
await rename7(tmpPath, filePath);
|
|
3703
|
+
await chmod6(filePath, 384);
|
|
3414
3704
|
}
|
|
3415
3705
|
async getConfig() {
|
|
3416
3706
|
if (!this.config) {
|
|
3417
3707
|
try {
|
|
3418
|
-
const raw = await
|
|
3708
|
+
const raw = await readFile7(join6(CONFIG_DIR, CONFIG_FILE), "utf-8");
|
|
3419
3709
|
const parsed = UserConfigSchema.safeParse(JSON.parse(raw));
|
|
3420
3710
|
this.config = parsed.success ? parsed.data : {};
|
|
3421
3711
|
} catch {
|
|
@@ -3428,21 +3718,22 @@ var UserConfigManager = class {
|
|
|
3428
3718
|
this.config = config;
|
|
3429
3719
|
await this.atomicWrite(config);
|
|
3430
3720
|
}
|
|
3431
|
-
async getEditorPreferences(
|
|
3721
|
+
async getEditorPreferences(ref) {
|
|
3432
3722
|
const config = await this.getConfig();
|
|
3433
|
-
return config[
|
|
3723
|
+
return config[configKey(ref)]?.settings?.editors ?? config[ref.id]?.settings?.editors ?? {};
|
|
3434
3724
|
}
|
|
3435
|
-
async setEditorPreference(
|
|
3725
|
+
async setEditorPreference(ref, target, editorKey) {
|
|
3436
3726
|
const config = await this.getConfig();
|
|
3437
|
-
|
|
3438
|
-
if (!config[
|
|
3439
|
-
if (!config[
|
|
3440
|
-
config[
|
|
3727
|
+
const key = configKey(ref);
|
|
3728
|
+
if (!config[key]) config[key] = { settings: config[ref.id]?.settings };
|
|
3729
|
+
if (!config[key].settings) config[key].settings = {};
|
|
3730
|
+
if (!config[key].settings.editors) config[key].settings.editors = {};
|
|
3731
|
+
config[key].settings.editors[target] = editorKey;
|
|
3441
3732
|
await this.saveConfig(config);
|
|
3442
3733
|
}
|
|
3443
|
-
async removeEditorPreference(
|
|
3734
|
+
async removeEditorPreference(ref, target) {
|
|
3444
3735
|
const config = await this.getConfig();
|
|
3445
|
-
const editors = config[
|
|
3736
|
+
const editors = config[configKey(ref)]?.settings?.editors;
|
|
3446
3737
|
if (editors && target in editors) {
|
|
3447
3738
|
delete editors[target];
|
|
3448
3739
|
await this.saveConfig(config);
|
|
@@ -3465,13 +3756,12 @@ async function getServiceCwd(serviceId) {
|
|
|
3465
3756
|
if (!service) {
|
|
3466
3757
|
throw new TRPCError8({ code: "NOT_FOUND", message: "Service not found" });
|
|
3467
3758
|
}
|
|
3468
|
-
|
|
3469
|
-
return resolveCwd(rawCwd);
|
|
3759
|
+
return resolveCwd(await resolveWorkspaceCwd({ serviceId }));
|
|
3470
3760
|
}
|
|
3471
3761
|
async function isCommandAvailable(command) {
|
|
3472
|
-
return new Promise((
|
|
3762
|
+
return new Promise((resolve7) => {
|
|
3473
3763
|
execFile2("which", [command], { timeout: 5e3 }, (err) => {
|
|
3474
|
-
|
|
3764
|
+
resolve7(!err);
|
|
3475
3765
|
});
|
|
3476
3766
|
});
|
|
3477
3767
|
}
|
|
@@ -3485,35 +3775,52 @@ var editorRouter = router({
|
|
|
3485
3775
|
);
|
|
3486
3776
|
return results;
|
|
3487
3777
|
}),
|
|
3488
|
-
getPreferences: protectedProcedure.input(z11.object({
|
|
3489
|
-
|
|
3778
|
+
getPreferences: protectedProcedure.input(z11.object({
|
|
3779
|
+
projectId: z11.string().max(128),
|
|
3780
|
+
orgId: z11.string().max(128).nullable().optional()
|
|
3781
|
+
})).query(async ({ input }) => {
|
|
3782
|
+
return userConfigManager.getEditorPreferences({ id: input.projectId, orgId: input.orgId });
|
|
3490
3783
|
}),
|
|
3491
3784
|
setDefault: protectedProcedure.input(z11.object({
|
|
3492
3785
|
projectId: z11.string().max(128),
|
|
3786
|
+
orgId: z11.string().max(128).nullable().optional(),
|
|
3493
3787
|
editorKey: z11.string().max(64)
|
|
3494
3788
|
})).mutation(async ({ input }) => {
|
|
3495
3789
|
if (!EDITOR_BY_KEY.has(input.editorKey)) {
|
|
3496
3790
|
throw new TRPCError8({ code: "BAD_REQUEST", message: `Unknown editor: ${input.editorKey}` });
|
|
3497
3791
|
}
|
|
3498
|
-
await userConfigManager.setEditorPreference(
|
|
3792
|
+
await userConfigManager.setEditorPreference(
|
|
3793
|
+
{ id: input.projectId, orgId: input.orgId },
|
|
3794
|
+
"default",
|
|
3795
|
+
input.editorKey
|
|
3796
|
+
);
|
|
3499
3797
|
return { success: true };
|
|
3500
3798
|
}),
|
|
3501
3799
|
setServiceEditor: protectedProcedure.input(z11.object({
|
|
3502
3800
|
projectId: z11.string().max(128),
|
|
3801
|
+
orgId: z11.string().max(128).nullable().optional(),
|
|
3503
3802
|
serviceId: z11.string().max(128),
|
|
3504
3803
|
editorKey: z11.string().max(64)
|
|
3505
3804
|
})).mutation(async ({ input }) => {
|
|
3506
3805
|
if (!EDITOR_BY_KEY.has(input.editorKey)) {
|
|
3507
3806
|
throw new TRPCError8({ code: "BAD_REQUEST", message: `Unknown editor: ${input.editorKey}` });
|
|
3508
3807
|
}
|
|
3509
|
-
await userConfigManager.setEditorPreference(
|
|
3808
|
+
await userConfigManager.setEditorPreference(
|
|
3809
|
+
{ id: input.projectId, orgId: input.orgId },
|
|
3810
|
+
input.serviceId,
|
|
3811
|
+
input.editorKey
|
|
3812
|
+
);
|
|
3510
3813
|
return { success: true };
|
|
3511
3814
|
}),
|
|
3512
3815
|
removeServiceEditor: protectedProcedure.input(z11.object({
|
|
3513
3816
|
projectId: z11.string().max(128),
|
|
3817
|
+
orgId: z11.string().max(128).nullable().optional(),
|
|
3514
3818
|
serviceId: z11.string().max(128)
|
|
3515
3819
|
})).mutation(async ({ input }) => {
|
|
3516
|
-
await userConfigManager.removeEditorPreference(
|
|
3820
|
+
await userConfigManager.removeEditorPreference(
|
|
3821
|
+
{ id: input.projectId, orgId: input.orgId },
|
|
3822
|
+
input.serviceId
|
|
3823
|
+
);
|
|
3517
3824
|
return { success: true };
|
|
3518
3825
|
}),
|
|
3519
3826
|
open: protectedProcedure.input(z11.object({
|
|
@@ -3527,7 +3834,7 @@ var editorRouter = router({
|
|
|
3527
3834
|
const expanded = input.path.replace(/^~(\/|$)/, `${process.env["HOME"] ?? "/tmp"}$1`).replace(/\$\{(\w+)\}|\$(\w+)/g, (_, a, b) => process.env[a || b] ?? "");
|
|
3528
3835
|
const resolvedPath = resolve2(expanded || ".");
|
|
3529
3836
|
try {
|
|
3530
|
-
await
|
|
3837
|
+
await stat3(resolvedPath);
|
|
3531
3838
|
} catch (err) {
|
|
3532
3839
|
if (err instanceof TRPCError8) throw err;
|
|
3533
3840
|
throw new TRPCError8({ code: "BAD_REQUEST", message: `Path does not exist: ${resolvedPath}` });
|
|
@@ -3551,12 +3858,12 @@ var editorRouter = router({
|
|
|
3551
3858
|
throw new TRPCError8({ code: "BAD_REQUEST", message: `Unknown editor: ${input.editorKey}` });
|
|
3552
3859
|
}
|
|
3553
3860
|
const cwd = await getServiceCwd(input.serviceId);
|
|
3554
|
-
const filePath =
|
|
3861
|
+
const filePath = join7(cwd, input.relativePath);
|
|
3555
3862
|
if (!filePath.startsWith(cwd)) {
|
|
3556
3863
|
throw new TRPCError8({ code: "BAD_REQUEST", message: "Path traversal not allowed" });
|
|
3557
3864
|
}
|
|
3558
3865
|
try {
|
|
3559
|
-
await
|
|
3866
|
+
await stat3(filePath);
|
|
3560
3867
|
} catch {
|
|
3561
3868
|
throw new TRPCError8({ code: "BAD_REQUEST", message: `File does not exist: ${filePath}` });
|
|
3562
3869
|
}
|
|
@@ -3574,8 +3881,8 @@ var editorRouter = router({
|
|
|
3574
3881
|
// ../server/src/trpc/routers/api-keys.ts
|
|
3575
3882
|
import { z as z12 } from "zod";
|
|
3576
3883
|
import { TRPCError as TRPCError9 } from "@trpc/server";
|
|
3577
|
-
import { mkdir as
|
|
3578
|
-
import { join as
|
|
3884
|
+
import { mkdir as mkdir8, writeFile as writeFile8 } from "fs/promises";
|
|
3885
|
+
import { join as join8, dirname as dirname4 } from "path";
|
|
3579
3886
|
var ALLOWED_SKILL_PATHS = /* @__PURE__ */ new Set([
|
|
3580
3887
|
".claude/skills/runeya/SKILL.md",
|
|
3581
3888
|
".cursor/rules/runeya.mdc",
|
|
@@ -3622,9 +3929,9 @@ var apiKeysRouter = router({
|
|
|
3622
3929
|
if (!ALLOWED_SKILL_PATHS.has(input.relativePath)) {
|
|
3623
3930
|
throw new TRPCError9({ code: "BAD_REQUEST", message: "Chemin non autoris\xE9" });
|
|
3624
3931
|
}
|
|
3625
|
-
const abs =
|
|
3626
|
-
await
|
|
3627
|
-
await
|
|
3932
|
+
const abs = join8(await resolveWorkspaceCwd({}), input.relativePath);
|
|
3933
|
+
await mkdir8(dirname4(abs), { recursive: true });
|
|
3934
|
+
await writeFile8(abs, input.content, { encoding: "utf-8", mode: 420 });
|
|
3628
3935
|
return { path: input.relativePath };
|
|
3629
3936
|
})
|
|
3630
3937
|
});
|
|
@@ -3950,17 +4257,17 @@ function buildApiDocs(apiBaseUrl, apiToken) {
|
|
|
3950
4257
|
}
|
|
3951
4258
|
|
|
3952
4259
|
// ../server/src/services/claude-transcript-sync.ts
|
|
3953
|
-
import { readdir as readdir2, access, readFile as
|
|
4260
|
+
import { readdir as readdir2, access, readFile as readFile9 } from "fs/promises";
|
|
3954
4261
|
import { watch } from "fs";
|
|
3955
4262
|
import { homedir as homedir3 } from "os";
|
|
3956
|
-
import { join as
|
|
4263
|
+
import { join as join10 } from "path";
|
|
3957
4264
|
|
|
3958
4265
|
// ../server/src/services/conversation-store.ts
|
|
3959
4266
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3960
|
-
import { readFile as
|
|
3961
|
-
import { dirname as
|
|
4267
|
+
import { readFile as readFile8, writeFile as writeFile9, rename as rename8, mkdir as mkdir9, chmod as chmod7 } from "fs/promises";
|
|
4268
|
+
import { dirname as dirname5, join as join9 } from "path";
|
|
3962
4269
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
3963
|
-
var
|
|
4270
|
+
var FILENAME4 = "conversations.private.json";
|
|
3964
4271
|
async function localProjectId() {
|
|
3965
4272
|
const projects = await projectStore.list().catch(() => []);
|
|
3966
4273
|
return projects.find((p) => !p.orgId)?.id ?? null;
|
|
@@ -3980,11 +4287,11 @@ var ConversationStore = class {
|
|
|
3980
4287
|
* root, but a launch directory owning its own key keeps both together.
|
|
3981
4288
|
*/
|
|
3982
4289
|
getFilePath() {
|
|
3983
|
-
return keyedFilePath(
|
|
4290
|
+
return keyedFilePath(FILENAME4);
|
|
3984
4291
|
}
|
|
3985
4292
|
/** Where older versions kept it — read only, so an older Runeya keeps working. */
|
|
3986
4293
|
getLegacyFilePath() {
|
|
3987
|
-
return legacyMachineFilePath(
|
|
4294
|
+
return legacyMachineFilePath(FILENAME4);
|
|
3988
4295
|
}
|
|
3989
4296
|
/**
|
|
3990
4297
|
* Read the machine file, falling back to the launch directory of old.
|
|
@@ -3994,13 +4301,13 @@ var ConversationStore = class {
|
|
|
3994
4301
|
*/
|
|
3995
4302
|
async readFromDisk() {
|
|
3996
4303
|
try {
|
|
3997
|
-
return await
|
|
4304
|
+
return await readFile8(await this.getFilePath(), "utf-8");
|
|
3998
4305
|
} catch {
|
|
3999
|
-
return
|
|
4306
|
+
return readFile8(this.getLegacyFilePath(), "utf-8");
|
|
4000
4307
|
}
|
|
4001
4308
|
}
|
|
4002
4309
|
async ensureDir() {
|
|
4003
|
-
await
|
|
4310
|
+
await mkdir9(dirname5(await this.getFilePath()), { recursive: true });
|
|
4004
4311
|
}
|
|
4005
4312
|
encryptMessages(messages) {
|
|
4006
4313
|
return encryptValue(JSON.stringify(messages));
|
|
@@ -4064,16 +4371,17 @@ var ConversationStore = class {
|
|
|
4064
4371
|
return;
|
|
4065
4372
|
}
|
|
4066
4373
|
this.loaded = true;
|
|
4374
|
+
await ensureProjectsLoaded();
|
|
4067
4375
|
const vaults = await allVaultRoots();
|
|
4068
|
-
const legacyPaths = [this.getLegacyFilePath(), await keyedFilePath(
|
|
4376
|
+
const legacyPaths = [this.getLegacyFilePath(), await keyedFilePath(FILENAME4)];
|
|
4069
4377
|
const sources = [
|
|
4070
4378
|
...legacyPaths.map((path) => ({ path, legacy: true })),
|
|
4071
|
-
...vaults.map((root) => ({ path:
|
|
4379
|
+
...vaults.map((root) => ({ path: join9(root, FILENAME4), legacy: false }))
|
|
4072
4380
|
];
|
|
4073
4381
|
for (const { path, legacy } of sources) {
|
|
4074
4382
|
let data;
|
|
4075
4383
|
try {
|
|
4076
|
-
data = JSON.parse(await
|
|
4384
|
+
data = JSON.parse(await readFile8(path, "utf-8"));
|
|
4077
4385
|
} catch {
|
|
4078
4386
|
continue;
|
|
4079
4387
|
}
|
|
@@ -4083,7 +4391,7 @@ var ConversationStore = class {
|
|
|
4083
4391
|
const conv = this.fromDisk(item);
|
|
4084
4392
|
if (!conv.projectId && inherited) conv.projectId = inherited;
|
|
4085
4393
|
this.conversations.set(item.id, conv);
|
|
4086
|
-
this.roots.set(item.id,
|
|
4394
|
+
this.roots.set(item.id, dirname5(path));
|
|
4087
4395
|
} catch {
|
|
4088
4396
|
}
|
|
4089
4397
|
}
|
|
@@ -4108,12 +4416,12 @@ var ConversationStore = class {
|
|
|
4108
4416
|
byRoot.set(root, bucket);
|
|
4109
4417
|
}
|
|
4110
4418
|
for (const [root, data] of byRoot) {
|
|
4111
|
-
await
|
|
4112
|
-
const filePath =
|
|
4419
|
+
await mkdir9(root, { recursive: true });
|
|
4420
|
+
const filePath = join9(root, FILENAME4);
|
|
4113
4421
|
const tmpPath = `${filePath}.tmp`;
|
|
4114
|
-
await
|
|
4115
|
-
await
|
|
4116
|
-
await
|
|
4422
|
+
await writeFile9(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
4423
|
+
await rename8(tmpPath, filePath);
|
|
4424
|
+
await chmod7(filePath, 384);
|
|
4117
4425
|
}
|
|
4118
4426
|
}
|
|
4119
4427
|
/**
|
|
@@ -4123,8 +4431,22 @@ var ConversationStore = class {
|
|
|
4123
4431
|
* key's own directory — never lost for want of an owner.
|
|
4124
4432
|
*/
|
|
4125
4433
|
async rootFor(conv) {
|
|
4126
|
-
|
|
4127
|
-
|
|
4434
|
+
const known = this.roots.get(conv.id) ?? null;
|
|
4435
|
+
if (conv.projectId) return rootOfProjectAmong(conv.projectId, known);
|
|
4436
|
+
return known ?? dirname5(await keyedFilePath(FILENAME4));
|
|
4437
|
+
}
|
|
4438
|
+
/**
|
|
4439
|
+
* Le projet d'une conversation, organisation comprise.
|
|
4440
|
+
*
|
|
4441
|
+
* Ce que la conversation ne dit pas d'elle-même : elle ne nomme que l'id.
|
|
4442
|
+
* Le coffre d'où elle vient fournit l'organisation, et c'est ce couple qu'il
|
|
4443
|
+
* faut pour retrouver le répertoire de travail du projet.
|
|
4444
|
+
*/
|
|
4445
|
+
async projectRefOf(conv) {
|
|
4446
|
+
if (!conv.projectId) return null;
|
|
4447
|
+
const known = this.roots.get(conv.id) ?? null;
|
|
4448
|
+
const ref = refOfProjectAmong(conv.projectId, known);
|
|
4449
|
+
return { id: ref.id, orgId: ref.orgId ?? null };
|
|
4128
4450
|
}
|
|
4129
4451
|
async save() {
|
|
4130
4452
|
const write = this.saveQueue.then(() => this.saveInternal());
|
|
@@ -4140,7 +4462,7 @@ var ConversationStore = class {
|
|
|
4140
4462
|
await this.load();
|
|
4141
4463
|
return this.conversations.get(id) ?? null;
|
|
4142
4464
|
}
|
|
4143
|
-
async create(title, isCli = false, workflowId, workflowType, runner, kanbanBoardId, kanbanCardId, providerId, projectId) {
|
|
4465
|
+
async create(title, isCli = false, workflowId, workflowType, runner, kanbanBoardId, kanbanCardId, providerId, projectId, orgId) {
|
|
4144
4466
|
await this.load();
|
|
4145
4467
|
const now = Date.now();
|
|
4146
4468
|
const conv = {
|
|
@@ -4159,6 +4481,9 @@ var ConversationStore = class {
|
|
|
4159
4481
|
updatedAt: now
|
|
4160
4482
|
};
|
|
4161
4483
|
this.conversations.set(conv.id, conv);
|
|
4484
|
+
if (projectId && orgId !== void 0) {
|
|
4485
|
+
this.roots.set(conv.id, vaultFor({ id: projectId, orgId }).root);
|
|
4486
|
+
}
|
|
4162
4487
|
await this.save();
|
|
4163
4488
|
this.events.emit("changed", { type: "created", conversation: this.snapshotConversation(conv) });
|
|
4164
4489
|
return conv;
|
|
@@ -4221,7 +4546,7 @@ var ConversationStore = class {
|
|
|
4221
4546
|
var conversationStore = new ConversationStore();
|
|
4222
4547
|
|
|
4223
4548
|
// ../server/src/services/claude-transcript-sync.ts
|
|
4224
|
-
var PROJECTS_DIR =
|
|
4549
|
+
var PROJECTS_DIR = join10(homedir3(), ".claude", "projects");
|
|
4225
4550
|
var DEFAULT_TITLES = /* @__PURE__ */ new Set(["", "New conversation", "Nouvelle conversation"]);
|
|
4226
4551
|
async function findClaudeSessionFile(sessionId) {
|
|
4227
4552
|
let entries;
|
|
@@ -4232,7 +4557,7 @@ async function findClaudeSessionFile(sessionId) {
|
|
|
4232
4557
|
}
|
|
4233
4558
|
const filename = `${sessionId}.jsonl`;
|
|
4234
4559
|
for (const dir of entries) {
|
|
4235
|
-
const candidate =
|
|
4560
|
+
const candidate = join10(PROJECTS_DIR, dir, filename);
|
|
4236
4561
|
try {
|
|
4237
4562
|
await access(candidate);
|
|
4238
4563
|
return candidate;
|
|
@@ -4247,7 +4572,7 @@ async function claudeSessionExists(sessionId) {
|
|
|
4247
4572
|
async function readClaudeTranscript(sessionId) {
|
|
4248
4573
|
const path = await findClaudeSessionFile(sessionId);
|
|
4249
4574
|
if (!path) return { found: false, path: null, entries: [] };
|
|
4250
|
-
const raw = await
|
|
4575
|
+
const raw = await readFile9(path, "utf-8");
|
|
4251
4576
|
const entries = [];
|
|
4252
4577
|
for (const line of raw.split("\n")) {
|
|
4253
4578
|
if (!line.trim()) continue;
|
|
@@ -4395,7 +4720,7 @@ async function startClaudeTranscriptWatcher() {
|
|
|
4395
4720
|
}
|
|
4396
4721
|
for (const dir of topLevel) {
|
|
4397
4722
|
try {
|
|
4398
|
-
const w = watch(
|
|
4723
|
+
const w = watch(join10(PROJECTS_DIR, dir), (_event, filename) => {
|
|
4399
4724
|
handleFsEvent(filename ? filename.toString() : null);
|
|
4400
4725
|
});
|
|
4401
4726
|
w.on("error", () => {
|
|
@@ -4421,8 +4746,8 @@ function stopClaudeTranscriptWatcher() {
|
|
|
4421
4746
|
// ../server/src/services/codex-sessions.ts
|
|
4422
4747
|
import { readdir as readdir3 } from "fs/promises";
|
|
4423
4748
|
import { homedir as homedir4 } from "os";
|
|
4424
|
-
import { join as
|
|
4425
|
-
var SESSIONS_DIR =
|
|
4749
|
+
import { join as join11 } from "path";
|
|
4750
|
+
var SESSIONS_DIR = join11(homedir4(), ".codex", "sessions");
|
|
4426
4751
|
async function findCodexSessionFile(sessionId) {
|
|
4427
4752
|
const suffix = `-${sessionId}.jsonl`;
|
|
4428
4753
|
async function walk(dir) {
|
|
@@ -4433,7 +4758,7 @@ async function findCodexSessionFile(sessionId) {
|
|
|
4433
4758
|
return null;
|
|
4434
4759
|
}
|
|
4435
4760
|
for (const entry of entries) {
|
|
4436
|
-
const full =
|
|
4761
|
+
const full = join11(dir, entry.name);
|
|
4437
4762
|
if (entry.isDirectory()) {
|
|
4438
4763
|
const found = await walk(full);
|
|
4439
4764
|
if (found) return found;
|
|
@@ -4808,7 +5133,7 @@ function parse(text) {
|
|
|
4808
5133
|
return result;
|
|
4809
5134
|
}
|
|
4810
5135
|
function getClaudeUsage() {
|
|
4811
|
-
return new Promise((
|
|
5136
|
+
return new Promise((resolve7, reject) => {
|
|
4812
5137
|
const env2 = { ...process.env };
|
|
4813
5138
|
delete env2["CLAUDECODE"];
|
|
4814
5139
|
let claudeBin = "claude";
|
|
@@ -4839,7 +5164,7 @@ function getClaudeUsage() {
|
|
|
4839
5164
|
clearTimeout(hardTimeout);
|
|
4840
5165
|
term?.kill();
|
|
4841
5166
|
const clean = buffer.replaceAll(/\x1b\[[0-9;]*X/g, "\n").replaceAll(/\x1b\[[0-9;]*[mGKHFABCDJsu]/g, "").replaceAll(/\x1b[^a-zA-Z]*[a-zA-Z]/g, "").replaceAll("\r", "");
|
|
4842
|
-
|
|
5167
|
+
resolve7(parse(clean));
|
|
4843
5168
|
}
|
|
4844
5169
|
const hardTimeout = setTimeout(() => {
|
|
4845
5170
|
if (!done) finish();
|
|
@@ -4973,7 +5298,7 @@ function parseCodexStatusText(text) {
|
|
|
4973
5298
|
return entries;
|
|
4974
5299
|
}
|
|
4975
5300
|
function getCodexUsageEntries() {
|
|
4976
|
-
return new Promise((
|
|
5301
|
+
return new Promise((resolve7, reject) => {
|
|
4977
5302
|
let term = null;
|
|
4978
5303
|
try {
|
|
4979
5304
|
term = pty2.spawn("codex", [], {
|
|
@@ -5003,7 +5328,7 @@ function getCodexUsageEntries() {
|
|
|
5003
5328
|
const debugClean = stripAnsi(buffer).slice(-6e3);
|
|
5004
5329
|
process.stdout.write("[codex-usage] raw tail:\n" + debugClean + "\n");
|
|
5005
5330
|
}
|
|
5006
|
-
|
|
5331
|
+
resolve7(parseCodexStatusText(buffer));
|
|
5007
5332
|
};
|
|
5008
5333
|
const hardTimeout = setTimeout(() => {
|
|
5009
5334
|
finish();
|
|
@@ -5393,12 +5718,12 @@ var ScenarioStore = class {
|
|
|
5393
5718
|
var scenarioStore = new ScenarioStore();
|
|
5394
5719
|
|
|
5395
5720
|
// ../server/src/services/scenario-run-store.ts
|
|
5396
|
-
import { mkdir as
|
|
5397
|
-
import { join as
|
|
5721
|
+
import { mkdir as mkdir11, rm as rm2, readFile as readFile11 } from "fs/promises";
|
|
5722
|
+
import { join as join12 } from "path";
|
|
5398
5723
|
|
|
5399
5724
|
// ../server/src/services/run-store-utils.ts
|
|
5400
|
-
import { chmod as
|
|
5401
|
-
import { dirname as
|
|
5725
|
+
import { chmod as chmod8, mkdir as mkdir10, rename as rename9, writeFile as writeFile10, readFile as readFile10 } from "fs/promises";
|
|
5726
|
+
import { dirname as dirname6, resolve as resolve3, sep } from "path";
|
|
5402
5727
|
var SAFE_PATH_SEGMENT_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;
|
|
5403
5728
|
function resolveConversationRunDir(rootDirName, conversationId) {
|
|
5404
5729
|
if (!SAFE_PATH_SEGMENT_RE.test(conversationId)) {
|
|
@@ -5412,18 +5737,18 @@ function resolveConversationRunDir(rootDirName, conversationId) {
|
|
|
5412
5737
|
return dir;
|
|
5413
5738
|
}
|
|
5414
5739
|
async function atomicWriteSecure(filePath, content) {
|
|
5415
|
-
await
|
|
5740
|
+
await mkdir10(dirname6(filePath), { recursive: true });
|
|
5416
5741
|
const tmpPath = `${filePath}.tmp`;
|
|
5417
|
-
await
|
|
5418
|
-
await
|
|
5419
|
-
await
|
|
5742
|
+
await writeFile10(tmpPath, content, "utf-8");
|
|
5743
|
+
await rename9(tmpPath, filePath);
|
|
5744
|
+
await chmod8(filePath, 384);
|
|
5420
5745
|
}
|
|
5421
5746
|
async function atomicWriteJsonSecure(filePath, data, pretty = false) {
|
|
5422
5747
|
await atomicWriteSecure(filePath, JSON.stringify(data, null, pretty ? 2 : 0));
|
|
5423
5748
|
}
|
|
5424
5749
|
async function readJsonFileSafe(filePath) {
|
|
5425
5750
|
try {
|
|
5426
|
-
const raw = await
|
|
5751
|
+
const raw = await readFile10(filePath, "utf-8");
|
|
5427
5752
|
return JSON.parse(raw);
|
|
5428
5753
|
} catch {
|
|
5429
5754
|
return null;
|
|
@@ -5446,7 +5771,7 @@ var ScenarioRunStore = class _ScenarioRunStore {
|
|
|
5446
5771
|
}
|
|
5447
5772
|
async initializeConversationWorkspace(conversationId, scenario, userInput) {
|
|
5448
5773
|
const dir = this.getRunDir(conversationId);
|
|
5449
|
-
await
|
|
5774
|
+
await mkdir11(dir, { recursive: true });
|
|
5450
5775
|
await this.saveScenarioSnapshot(conversationId, scenario);
|
|
5451
5776
|
const nodesById = new Map(scenario.nodes.map((p) => [p.id, p]));
|
|
5452
5777
|
const teamMap = new Map(scenario.groups.map((t2) => [t2.id, t2.name]));
|
|
@@ -5471,8 +5796,8 @@ var ScenarioRunStore = class _ScenarioRunStore {
|
|
|
5471
5796
|
"- Use read-only references from linked upstream files as context.",
|
|
5472
5797
|
""
|
|
5473
5798
|
].join("\n");
|
|
5474
|
-
await atomicWriteSecure(
|
|
5475
|
-
await atomicWriteSecure(
|
|
5799
|
+
await atomicWriteSecure(join12(dir, "README.md"), overview);
|
|
5800
|
+
await atomicWriteSecure(join12(dir, "USER_REQUEST.md"), `# User request
|
|
5476
5801
|
|
|
5477
5802
|
${userInput.trim() || "(empty)"}
|
|
5478
5803
|
`);
|
|
@@ -5498,7 +5823,7 @@ ${userInput.trim() || "(empty)"}
|
|
|
5498
5823
|
"- Waiting for assignment.",
|
|
5499
5824
|
""
|
|
5500
5825
|
].join("\n");
|
|
5501
|
-
await atomicWriteSecure(
|
|
5826
|
+
await atomicWriteSecure(join12(dir, fileName), content);
|
|
5502
5827
|
}
|
|
5503
5828
|
}
|
|
5504
5829
|
async deleteRunDir(conversationId) {
|
|
@@ -5558,7 +5883,7 @@ ${userInput.trim() || "(empty)"}
|
|
|
5558
5883
|
if (!runState) return;
|
|
5559
5884
|
try {
|
|
5560
5885
|
const dir = this.getRunDir(conversationId);
|
|
5561
|
-
await
|
|
5886
|
+
await mkdir11(dir, { recursive: true });
|
|
5562
5887
|
const data = {
|
|
5563
5888
|
nodeStates: { ...runState.nodeStates },
|
|
5564
5889
|
nodeOutputs: { ...runState.nodeOutputs },
|
|
@@ -5566,18 +5891,18 @@ ${userInput.trim() || "(empty)"}
|
|
|
5566
5891
|
...Object.keys(runState.nodeAgentIds).length > 0 ? { nodeAgentIds: { ...runState.nodeAgentIds } } : {},
|
|
5567
5892
|
...Object.keys(runState.waitingInputs).length > 0 ? { waitingInputs: { ...runState.waitingInputs } } : {}
|
|
5568
5893
|
};
|
|
5569
|
-
await atomicWriteJsonSecure(
|
|
5894
|
+
await atomicWriteJsonSecure(join12(dir, "run-state.json"), data);
|
|
5570
5895
|
} catch {
|
|
5571
5896
|
}
|
|
5572
5897
|
}
|
|
5573
5898
|
async loadPersistedRunState(conversationId) {
|
|
5574
5899
|
const dir = this.getRunDir(conversationId);
|
|
5575
|
-
return readJsonFileSafe(
|
|
5900
|
+
return readJsonFileSafe(join12(dir, "run-state.json"));
|
|
5576
5901
|
}
|
|
5577
5902
|
async readUserRequest(conversationId) {
|
|
5578
5903
|
try {
|
|
5579
5904
|
const dir = this.getRunDir(conversationId);
|
|
5580
|
-
const content = await
|
|
5905
|
+
const content = await readFile11(join12(dir, "USER_REQUEST.md"), "utf-8");
|
|
5581
5906
|
return content.replace(/^#\s*User request\s*\n+/, "").trim() || null;
|
|
5582
5907
|
} catch {
|
|
5583
5908
|
return null;
|
|
@@ -5586,23 +5911,23 @@ ${userInput.trim() || "(empty)"}
|
|
|
5586
5911
|
async readReadme(conversationId) {
|
|
5587
5912
|
try {
|
|
5588
5913
|
const dir = this.getRunDir(conversationId);
|
|
5589
|
-
return await
|
|
5914
|
+
return await readFile11(join12(dir, "README.md"), "utf-8");
|
|
5590
5915
|
} catch {
|
|
5591
5916
|
return null;
|
|
5592
5917
|
}
|
|
5593
5918
|
}
|
|
5594
5919
|
async saveScenarioSnapshot(conversationId, scenario) {
|
|
5595
5920
|
const dir = this.getRunDir(conversationId);
|
|
5596
|
-
await
|
|
5921
|
+
await mkdir11(dir, { recursive: true });
|
|
5597
5922
|
await atomicWriteJsonSecure(
|
|
5598
|
-
|
|
5923
|
+
join12(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE),
|
|
5599
5924
|
scenario,
|
|
5600
5925
|
true
|
|
5601
5926
|
);
|
|
5602
5927
|
}
|
|
5603
5928
|
async loadScenarioSnapshot(conversationId) {
|
|
5604
5929
|
const dir = this.getRunDir(conversationId);
|
|
5605
|
-
return readJsonFileSafe(
|
|
5930
|
+
return readJsonFileSafe(join12(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE));
|
|
5606
5931
|
}
|
|
5607
5932
|
};
|
|
5608
5933
|
var scenarioRunStore = new ScenarioRunStore();
|
|
@@ -5648,9 +5973,9 @@ async function fetchProviderModels(provider, plaintextKey) {
|
|
|
5648
5973
|
|
|
5649
5974
|
// ../server/src/services/image-storage.service.ts
|
|
5650
5975
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
5651
|
-
import { mkdir as
|
|
5976
|
+
import { mkdir as mkdir12, writeFile as writeFile11, readFile as readFile12, unlink as unlink2, readdir as readdir4 } from "fs/promises";
|
|
5652
5977
|
import { existsSync } from "fs";
|
|
5653
|
-
import { join as
|
|
5978
|
+
import { join as join13, extname, resolve as resolve4, sep as sep2 } from "path";
|
|
5654
5979
|
var ALLOWED_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
5655
5980
|
"image/png",
|
|
5656
5981
|
"image/jpeg",
|
|
@@ -5667,7 +5992,7 @@ var ImageStorageService = class {
|
|
|
5667
5992
|
this.dir = dir;
|
|
5668
5993
|
}
|
|
5669
5994
|
async ensureTmpDir() {
|
|
5670
|
-
await
|
|
5995
|
+
await mkdir12(this.dir, { recursive: true, mode: 448 });
|
|
5671
5996
|
}
|
|
5672
5997
|
async saveImage(buffer, originalName, mimeType, contextId) {
|
|
5673
5998
|
if (!ALLOWED_MIME_TYPES.has(mimeType)) {
|
|
@@ -5687,15 +6012,15 @@ var ImageStorageService = class {
|
|
|
5687
6012
|
const imageId = randomUUID4();
|
|
5688
6013
|
const safeExt = ext.replace(/[^a-z.]/g, "");
|
|
5689
6014
|
const fileName = `${imageId}${safeExt}`;
|
|
5690
|
-
const filePath =
|
|
6015
|
+
const filePath = join13(this.dir, fileName);
|
|
5691
6016
|
const resolvedPath = resolve4(filePath);
|
|
5692
6017
|
const resolvedDir = resolve4(this.dir);
|
|
5693
6018
|
if (resolvedPath !== resolvedDir && !resolvedPath.startsWith(resolvedDir + sep2)) {
|
|
5694
6019
|
throw new Error("Invalid file path");
|
|
5695
6020
|
}
|
|
5696
|
-
await
|
|
6021
|
+
await writeFile11(filePath, buffer, { mode: 384 });
|
|
5697
6022
|
const meta = { imageId, filePath, mimeType, originalName, createdAt: Date.now(), contextId };
|
|
5698
|
-
await
|
|
6023
|
+
await writeFile11(`${filePath}.meta.json`, JSON.stringify(meta), { encoding: "utf-8", mode: 384 });
|
|
5699
6024
|
return meta;
|
|
5700
6025
|
}
|
|
5701
6026
|
/**
|
|
@@ -5713,7 +6038,7 @@ var ImageStorageService = class {
|
|
|
5713
6038
|
const files = await readdir4(this.dir);
|
|
5714
6039
|
const metaFile = files.find((f) => f.startsWith(imageId) && f.endsWith(".meta.json"));
|
|
5715
6040
|
if (!metaFile) return null;
|
|
5716
|
-
const raw = JSON.parse(await
|
|
6041
|
+
const raw = JSON.parse(await readFile12(join13(this.dir, metaFile), "utf-8"));
|
|
5717
6042
|
if (expectedContextId !== void 0 && raw.contextId !== expectedContextId) return null;
|
|
5718
6043
|
if (!existsSync(raw.filePath)) return null;
|
|
5719
6044
|
return { filePath: raw.filePath, mimeType: raw.mimeType, contextId: raw.contextId };
|
|
@@ -5739,7 +6064,7 @@ var ImageStorageService = class {
|
|
|
5739
6064
|
await Promise.all(
|
|
5740
6065
|
files.filter((f) => f.endsWith(".meta.json")).map(async (file) => {
|
|
5741
6066
|
try {
|
|
5742
|
-
const raw = JSON.parse(await
|
|
6067
|
+
const raw = JSON.parse(await readFile12(join13(this.dir, file), "utf-8"));
|
|
5743
6068
|
if (raw.contextId === contextId) {
|
|
5744
6069
|
await unlink2(raw.filePath).catch(() => {
|
|
5745
6070
|
});
|
|
@@ -5754,7 +6079,7 @@ var ImageStorageService = class {
|
|
|
5754
6079
|
async readImageAsBase64(imageId, expectedContextId) {
|
|
5755
6080
|
const result = await this.getImagePath(imageId, expectedContextId);
|
|
5756
6081
|
if (!result) return null;
|
|
5757
|
-
const buffer = await
|
|
6082
|
+
const buffer = await readFile12(result.filePath);
|
|
5758
6083
|
return { base64: buffer.toString("base64"), mimeType: result.mimeType };
|
|
5759
6084
|
}
|
|
5760
6085
|
async listExpiredImages(ttlMs = IMAGE_TTL_MS) {
|
|
@@ -5769,7 +6094,7 @@ var ImageStorageService = class {
|
|
|
5769
6094
|
for (const file of files) {
|
|
5770
6095
|
if (!file.endsWith(".meta.json")) continue;
|
|
5771
6096
|
try {
|
|
5772
|
-
const raw = JSON.parse(await
|
|
6097
|
+
const raw = JSON.parse(await readFile12(join13(this.dir, file), "utf-8"));
|
|
5773
6098
|
if (now - raw.createdAt > ttlMs) expired.push(raw);
|
|
5774
6099
|
} catch {
|
|
5775
6100
|
}
|
|
@@ -5841,10 +6166,10 @@ import { basename } from "path";
|
|
|
5841
6166
|
import { TRPCError as TRPCError10 } from "@trpc/server";
|
|
5842
6167
|
|
|
5843
6168
|
// ../server/src/services/cloud-session-store.ts
|
|
5844
|
-
import { readFile as
|
|
5845
|
-
import { dirname as
|
|
6169
|
+
import { readFile as readFile13, writeFile as writeFile12, rename as rename10, mkdir as mkdir13, chmod as chmod9, unlink as unlink3 } from "fs/promises";
|
|
6170
|
+
import { dirname as dirname7 } from "path";
|
|
5846
6171
|
import { randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
5847
|
-
var
|
|
6172
|
+
var FILENAME5 = "cloud-session.json";
|
|
5848
6173
|
var STATE_TTL_MS = 5 * 60 * 1e3;
|
|
5849
6174
|
var CloudSessionStore = class {
|
|
5850
6175
|
session = null;
|
|
@@ -5856,11 +6181,11 @@ var CloudSessionStore = class {
|
|
|
5856
6181
|
* root, but a launch directory owning its own key keeps both together.
|
|
5857
6182
|
*/
|
|
5858
6183
|
getFilePath() {
|
|
5859
|
-
return keyedFilePath(
|
|
6184
|
+
return keyedFilePath(FILENAME5);
|
|
5860
6185
|
}
|
|
5861
6186
|
/** Where older versions kept it — read only, so an older Runeya keeps working. */
|
|
5862
6187
|
getLegacyFilePath() {
|
|
5863
|
-
return legacyMachineFilePath(
|
|
6188
|
+
return legacyMachineFilePath(FILENAME5);
|
|
5864
6189
|
}
|
|
5865
6190
|
/**
|
|
5866
6191
|
* Read the machine file, falling back to the launch directory of old.
|
|
@@ -5870,9 +6195,9 @@ var CloudSessionStore = class {
|
|
|
5870
6195
|
*/
|
|
5871
6196
|
async readFromDisk() {
|
|
5872
6197
|
try {
|
|
5873
|
-
return await
|
|
6198
|
+
return await readFile13(await this.getFilePath(), "utf-8");
|
|
5874
6199
|
} catch {
|
|
5875
|
-
return
|
|
6200
|
+
return readFile13(this.getLegacyFilePath(), "utf-8");
|
|
5876
6201
|
}
|
|
5877
6202
|
}
|
|
5878
6203
|
async load() {
|
|
@@ -5919,12 +6244,12 @@ var CloudSessionStore = class {
|
|
|
5919
6244
|
}
|
|
5920
6245
|
}
|
|
5921
6246
|
async save() {
|
|
5922
|
-
await
|
|
6247
|
+
await mkdir13(dirname7(await this.getFilePath()), { recursive: true });
|
|
5923
6248
|
const filePath = await this.getFilePath();
|
|
5924
6249
|
const tmpPath = filePath + ".tmp";
|
|
5925
|
-
await
|
|
5926
|
-
await
|
|
5927
|
-
await
|
|
6250
|
+
await writeFile12(tmpPath, JSON.stringify(this.session, null, 2), "utf-8");
|
|
6251
|
+
await rename10(tmpPath, filePath);
|
|
6252
|
+
await chmod9(filePath, 384);
|
|
5928
6253
|
}
|
|
5929
6254
|
/** Store the session token obtained from the one-time-token exchange. */
|
|
5930
6255
|
async set(cloudUrl, token) {
|
|
@@ -5942,6 +6267,18 @@ var CloudSessionStore = class {
|
|
|
5942
6267
|
return null;
|
|
5943
6268
|
}
|
|
5944
6269
|
}
|
|
6270
|
+
/**
|
|
6271
|
+
* The cloud this machine is signed into, or null when not linked.
|
|
6272
|
+
*
|
|
6273
|
+
* Needed to tell "an org I am not a member of" apart from "an org of another
|
|
6274
|
+
* cloud instance entirely" — the ids of two clouds share no namespace, so
|
|
6275
|
+
* comparing them without this would call every foreign-cloud project a
|
|
6276
|
+
* permission problem.
|
|
6277
|
+
*/
|
|
6278
|
+
async getCloudUrl() {
|
|
6279
|
+
await this.load();
|
|
6280
|
+
return this.session?.cloudUrl ?? null;
|
|
6281
|
+
}
|
|
5945
6282
|
async isLinked() {
|
|
5946
6283
|
return await this.getToken() !== null;
|
|
5947
6284
|
}
|
|
@@ -6017,9 +6354,10 @@ var cloudAuthRouter = router({
|
|
|
6017
6354
|
const url = `${getCloudAppUrl()}/link-local?callback=${encodeURIComponent(callback)}&state=${encodeURIComponent(state)}`;
|
|
6018
6355
|
return { url };
|
|
6019
6356
|
}),
|
|
6020
|
-
/** Whether this machine holds a cloud session. */
|
|
6357
|
+
/** Whether this machine holds a cloud session, and against which cloud. */
|
|
6021
6358
|
status: protectedProcedure.query(async () => {
|
|
6022
|
-
|
|
6359
|
+
const linked = await cloudSessionStore.isLinked();
|
|
6360
|
+
return { linked, cloudUrl: linked ? await cloudSessionStore.getCloudUrl() : null };
|
|
6023
6361
|
}),
|
|
6024
6362
|
/** The cloud session (user + org), fetched with the stored token. */
|
|
6025
6363
|
session: protectedProcedure.query(async () => {
|
|
@@ -6569,12 +6907,12 @@ function tailTranscript(opts) {
|
|
|
6569
6907
|
const poll = setInterval(() => {
|
|
6570
6908
|
void readMore();
|
|
6571
6909
|
}, 500);
|
|
6572
|
-
await new Promise((
|
|
6910
|
+
await new Promise((resolve7) => {
|
|
6573
6911
|
const tick = setInterval(() => {
|
|
6574
6912
|
if (stopped) {
|
|
6575
6913
|
clearInterval(tick);
|
|
6576
6914
|
clearInterval(poll);
|
|
6577
|
-
|
|
6915
|
+
resolve7();
|
|
6578
6916
|
}
|
|
6579
6917
|
}, 100);
|
|
6580
6918
|
});
|
|
@@ -6593,7 +6931,7 @@ function tailTranscript(opts) {
|
|
|
6593
6931
|
|
|
6594
6932
|
// ../server/src/services/ai-runners/claude-pty-runner.ts
|
|
6595
6933
|
async function getAgentManager() {
|
|
6596
|
-
const { agentManager: agentManager2 } = await import("./agent-manager-
|
|
6934
|
+
const { agentManager: agentManager2 } = await import("./agent-manager-JJYZDLNB.js");
|
|
6597
6935
|
return agentManager2;
|
|
6598
6936
|
}
|
|
6599
6937
|
function buildPtyArgs(params, sessionId, opts) {
|
|
@@ -6606,7 +6944,7 @@ ${apiDocs}` : systemContext;
|
|
|
6606
6944
|
const systemPrompt = skipTokenInstructions ? fullSystemContext : `${fullSystemContext}
|
|
6607
6945
|
|
|
6608
6946
|
${TOKEN_INSTRUCTIONS}`;
|
|
6609
|
-
const mcpArgs = mcpConfig && !opts.excludeMcp ? ["--mcp-config", mcpConfig] : [];
|
|
6947
|
+
const mcpArgs = mcpConfig && !opts.excludeMcp ? ["--mcp-config", mcpConfig, "--strict-mcp-config"] : [];
|
|
6610
6948
|
const args = [
|
|
6611
6949
|
...sessionArg,
|
|
6612
6950
|
"--dangerously-skip-permissions",
|
|
@@ -6672,7 +7010,7 @@ async function runClaudeViaPty(params) {
|
|
|
6672
7010
|
return a;
|
|
6673
7011
|
});
|
|
6674
7012
|
params.onCommand?.(`claude ${displayArgs.join(" ")}`);
|
|
6675
|
-
return new Promise((
|
|
7013
|
+
return new Promise((resolve7, reject) => {
|
|
6676
7014
|
let settled = false;
|
|
6677
7015
|
let spawnId = null;
|
|
6678
7016
|
const state = { assistantText: "", tools: /* @__PURE__ */ new Map() };
|
|
@@ -6695,7 +7033,7 @@ async function runClaudeViaPty(params) {
|
|
|
6695
7033
|
settled = true;
|
|
6696
7034
|
cleanup();
|
|
6697
7035
|
void killSpawn();
|
|
6698
|
-
|
|
7036
|
+
resolve7(result);
|
|
6699
7037
|
};
|
|
6700
7038
|
const fail = (err) => {
|
|
6701
7039
|
if (settled) return;
|
|
@@ -6900,7 +7238,7 @@ ${apiDocs}` : systemContext;
|
|
|
6900
7238
|
|
|
6901
7239
|
${TOKEN_INSTRUCTIONS}`;
|
|
6902
7240
|
const inputArgs = hasImages ? ["--input-format", "stream-json"] : ["-p", prompt];
|
|
6903
|
-
const mcpArgs = mcpConfig && !opts?.excludeMcp ? ["--mcp-config", mcpConfig] : [];
|
|
7241
|
+
const mcpArgs = mcpConfig && !opts?.excludeMcp ? ["--mcp-config", mcpConfig, "--strict-mcp-config"] : [];
|
|
6904
7242
|
const args = [
|
|
6905
7243
|
...sessionArg,
|
|
6906
7244
|
"--output-format",
|
|
@@ -6999,7 +7337,7 @@ var ClaudeCodeRunner = class {
|
|
|
6999
7337
|
runLocal(params) {
|
|
7000
7338
|
const { signal, skipTokenInstructions } = params;
|
|
7001
7339
|
const { args, env: env2, stdinData, sessionArg, hasImages: _hasImages } = buildClaudeSpawnConfig(params);
|
|
7002
|
-
return new Promise((
|
|
7340
|
+
return new Promise((resolve7, reject) => {
|
|
7003
7341
|
const totalArgSize = args.reduce((s, a) => s + a.length + 1, 0);
|
|
7004
7342
|
console.log(`[claude-code-runner] arg size: ${totalArgSize} bytes (${(totalArgSize / 1024 / 1024).toFixed(2)} MB), session: ${sessionArg.join(" ")}, images: ${params.images?.length ?? 0}`);
|
|
7005
7343
|
const displayArgs = args.map((a) => {
|
|
@@ -7041,7 +7379,7 @@ var ClaudeCodeRunner = class {
|
|
|
7041
7379
|
});
|
|
7042
7380
|
proc.on("close", (code) => {
|
|
7043
7381
|
if (ctx.earlyResult) {
|
|
7044
|
-
|
|
7382
|
+
resolve7({ ...ctx.earlyResult, actualSessionId: streamState.actualSessionId });
|
|
7045
7383
|
return;
|
|
7046
7384
|
}
|
|
7047
7385
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -7058,7 +7396,7 @@ var ClaudeCodeRunner = class {
|
|
|
7058
7396
|
return;
|
|
7059
7397
|
}
|
|
7060
7398
|
const { status, message, branchChoice } = skipTokenInstructions ? { status: "none", message: "", branchChoice: void 0 } : parseEndToken(streamState.fullContent);
|
|
7061
|
-
|
|
7399
|
+
resolve7({ output: streamState.fullContent, tokenStatus: status, tokenMessage: message, branchChoice, actualSessionId: streamState.actualSessionId });
|
|
7062
7400
|
});
|
|
7063
7401
|
proc.on("error", (err) => {
|
|
7064
7402
|
if (ctx.settled) return;
|
|
@@ -7114,12 +7452,12 @@ var CodexAppServerClient = class extends EventEmitter3 {
|
|
|
7114
7452
|
if (forceId !== void 0 && forceId >= this.nextId) {
|
|
7115
7453
|
this.nextId = forceId + 1;
|
|
7116
7454
|
}
|
|
7117
|
-
return new Promise((
|
|
7455
|
+
return new Promise((resolve7, reject) => {
|
|
7118
7456
|
const timer = setTimeout(() => {
|
|
7119
7457
|
this.pending.delete(id);
|
|
7120
7458
|
reject(new Error(`Request ${method} (id=${id}) timed out after ${this.requestTimeoutMs}ms`));
|
|
7121
7459
|
}, this.requestTimeoutMs);
|
|
7122
|
-
this.pending.set(id, { resolve:
|
|
7460
|
+
this.pending.set(id, { resolve: resolve7, reject, timer });
|
|
7123
7461
|
this.writeLine({ jsonrpc: "2.0", method, id, params: params ?? {} });
|
|
7124
7462
|
});
|
|
7125
7463
|
}
|
|
@@ -7151,8 +7489,8 @@ var CodexAppServerClient = class extends EventEmitter3 {
|
|
|
7151
7489
|
this.rl = null;
|
|
7152
7490
|
const proc = this.proc;
|
|
7153
7491
|
this.proc = null;
|
|
7154
|
-
return new Promise((
|
|
7155
|
-
proc.on("exit", () =>
|
|
7492
|
+
return new Promise((resolve7) => {
|
|
7493
|
+
proc.on("exit", () => resolve7());
|
|
7156
7494
|
proc.kill("SIGTERM");
|
|
7157
7495
|
const forceTimer = setTimeout(() => {
|
|
7158
7496
|
proc.kill("SIGKILL");
|
|
@@ -7246,7 +7584,7 @@ async function shutdownCodexAppServer() {
|
|
|
7246
7584
|
}
|
|
7247
7585
|
}
|
|
7248
7586
|
async function getAgentManager2() {
|
|
7249
|
-
const { agentManager: agentManager2 } = await import("./agent-manager-
|
|
7587
|
+
const { agentManager: agentManager2 } = await import("./agent-manager-JJYZDLNB.js");
|
|
7250
7588
|
return agentManager2;
|
|
7251
7589
|
}
|
|
7252
7590
|
function parseRuneyaCurlCommand(command) {
|
|
@@ -7335,7 +7673,7 @@ var CodexRunner = class {
|
|
|
7335
7673
|
method: "thread/start",
|
|
7336
7674
|
params: {
|
|
7337
7675
|
model: model || void 0,
|
|
7338
|
-
cwd: process.cwd(),
|
|
7676
|
+
cwd: params.cwd ?? process.cwd(),
|
|
7339
7677
|
approvalPolicy: "never",
|
|
7340
7678
|
sandbox: "danger-full-access"
|
|
7341
7679
|
},
|
|
@@ -7361,7 +7699,7 @@ ${prompt}`;
|
|
|
7361
7699
|
}
|
|
7362
7700
|
input.push({ type: "text", text: fullPrompt });
|
|
7363
7701
|
const turnRef = { id: null };
|
|
7364
|
-
return new Promise((
|
|
7702
|
+
return new Promise((resolve7, reject) => {
|
|
7365
7703
|
let settled = false;
|
|
7366
7704
|
let fullContent = "";
|
|
7367
7705
|
let thinkingEmitted = false;
|
|
@@ -7410,7 +7748,7 @@ ${prompt}`;
|
|
|
7410
7748
|
params.onDelta(delta);
|
|
7411
7749
|
const { status, message, branchChoice } = parseEndToken(fullContent);
|
|
7412
7750
|
if (status !== "none") {
|
|
7413
|
-
settle(() =>
|
|
7751
|
+
settle(() => resolve7({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
|
|
7414
7752
|
}
|
|
7415
7753
|
}
|
|
7416
7754
|
} else if (method === "item/reasoning/summaryTextDelta" || method === "item/reasoning/textDelta") {
|
|
@@ -7484,7 +7822,7 @@ ${prompt}`;
|
|
|
7484
7822
|
return;
|
|
7485
7823
|
}
|
|
7486
7824
|
const { status, message, branchChoice } = parseEndToken(fullContent);
|
|
7487
|
-
settle(() =>
|
|
7825
|
+
settle(() => resolve7({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
|
|
7488
7826
|
} else if (method === "turn/errored") {
|
|
7489
7827
|
const error = p["error"];
|
|
7490
7828
|
const msg = typeof error?.["message"] === "string" ? error["message"] : "Codex turn errored";
|
|
@@ -7527,7 +7865,7 @@ ${prompt}`;
|
|
|
7527
7865
|
} else {
|
|
7528
7866
|
const res = await client.sendRequest("thread/start", {
|
|
7529
7867
|
model: model || void 0,
|
|
7530
|
-
cwd: process.cwd(),
|
|
7868
|
+
cwd: params.cwd ?? process.cwd(),
|
|
7531
7869
|
approvalPolicy: "never",
|
|
7532
7870
|
sandbox: "danger-full-access"
|
|
7533
7871
|
});
|
|
@@ -7559,7 +7897,7 @@ ${prompt}`;
|
|
|
7559
7897
|
sandboxPolicy: { type: "dangerFullAccess" }
|
|
7560
7898
|
});
|
|
7561
7899
|
const turnId = turnRes.turn.id;
|
|
7562
|
-
return new Promise((
|
|
7900
|
+
return new Promise((resolve7, reject) => {
|
|
7563
7901
|
let settled = false;
|
|
7564
7902
|
let fullContent = "";
|
|
7565
7903
|
let thinkingEmitted = false;
|
|
@@ -7585,7 +7923,7 @@ ${prompt}`;
|
|
|
7585
7923
|
params.onDelta(delta);
|
|
7586
7924
|
const { status, message, branchChoice } = parseEndToken(fullContent);
|
|
7587
7925
|
if (status !== "none") {
|
|
7588
|
-
settle(() =>
|
|
7926
|
+
settle(() => resolve7({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
|
|
7589
7927
|
}
|
|
7590
7928
|
}
|
|
7591
7929
|
} else if (method === "item/reasoning/summaryTextDelta" || method === "item/reasoning/textDelta") {
|
|
@@ -7659,7 +7997,7 @@ ${prompt}`;
|
|
|
7659
7997
|
return;
|
|
7660
7998
|
}
|
|
7661
7999
|
const { status, message, branchChoice } = parseEndToken(fullContent);
|
|
7662
|
-
settle(() =>
|
|
8000
|
+
settle(() => resolve7({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
|
|
7663
8001
|
} else if (method === "turn/errored") {
|
|
7664
8002
|
const error = p["error"];
|
|
7665
8003
|
const msg = typeof error?.["message"] === "string" ? error["message"] : "Codex turn errored";
|
|
@@ -7985,6 +8323,8 @@ async function startClaudeCodeSession(convId, prompt, opts = {}) {
|
|
|
7985
8323
|
const injectedEnv = await resolveInjectedEnv(opts.environmentId);
|
|
7986
8324
|
const done = new ClaudeCodeRunner().run({
|
|
7987
8325
|
agentId: resolvedAgentId ?? void 0,
|
|
8326
|
+
// The conversation's project, not the server's launch directory.
|
|
8327
|
+
cwd: await conversationCwd(conv),
|
|
7988
8328
|
sessionId: conv.id,
|
|
7989
8329
|
prompt,
|
|
7990
8330
|
isResume,
|
|
@@ -8047,6 +8387,7 @@ async function startCodexSession(convId, prompt, opts = {}) {
|
|
|
8047
8387
|
const injectedEnv = await resolveInjectedEnv(opts.environmentId);
|
|
8048
8388
|
const done = new CodexRunner().run({
|
|
8049
8389
|
agentId: resolvedAgentId ?? void 0,
|
|
8390
|
+
cwd: await conversationCwd(conv),
|
|
8050
8391
|
sessionId: conv.codexThreadId ?? "",
|
|
8051
8392
|
prompt,
|
|
8052
8393
|
isResume,
|
|
@@ -8097,19 +8438,24 @@ var StoredMessageSchema = z15.object({
|
|
|
8097
8438
|
content: z15.string().max(1e5),
|
|
8098
8439
|
parts: z15.array(MessagePartSchema).max(200).optional()
|
|
8099
8440
|
});
|
|
8441
|
+
async function conversationCwd(conv) {
|
|
8442
|
+
const ref = await conversationStore.projectRefOf(conv);
|
|
8443
|
+
if (!ref) return resolveAgentCwd({});
|
|
8444
|
+
return resolveAgentCwd({ projectId: ref.id, orgId: ref.orgId });
|
|
8445
|
+
}
|
|
8100
8446
|
var chatRouter = router({
|
|
8101
8447
|
conversations: router({
|
|
8102
8448
|
onChanged: protectedProcedure.subscription(async function* () {
|
|
8103
8449
|
const initial = await conversationStore.list();
|
|
8104
8450
|
yield { type: "initial", conversations: initial };
|
|
8105
8451
|
const queue = [];
|
|
8106
|
-
let
|
|
8452
|
+
let resolve7 = null;
|
|
8107
8453
|
const handler = (event) => {
|
|
8108
8454
|
if (queue.length >= 500) queue.shift();
|
|
8109
8455
|
queue.push(event);
|
|
8110
|
-
if (
|
|
8111
|
-
|
|
8112
|
-
|
|
8456
|
+
if (resolve7) {
|
|
8457
|
+
resolve7();
|
|
8458
|
+
resolve7 = null;
|
|
8113
8459
|
}
|
|
8114
8460
|
};
|
|
8115
8461
|
conversationStore.onChanged(handler);
|
|
@@ -8119,7 +8465,7 @@ var chatRouter = router({
|
|
|
8119
8465
|
yield queue.shift();
|
|
8120
8466
|
} else {
|
|
8121
8467
|
await new Promise((r) => {
|
|
8122
|
-
|
|
8468
|
+
resolve7 = r;
|
|
8123
8469
|
});
|
|
8124
8470
|
}
|
|
8125
8471
|
}
|
|
@@ -8157,7 +8503,8 @@ var chatRouter = router({
|
|
|
8157
8503
|
kanbanCardId: Id128Schema.optional(),
|
|
8158
8504
|
// The project the conversation is about: only the caller knows which
|
|
8159
8505
|
// one it is looking at.
|
|
8160
|
-
projectId: Id128Schema.optional()
|
|
8506
|
+
projectId: Id128Schema.optional(),
|
|
8507
|
+
orgId: z15.string().max(128).nullable().optional()
|
|
8161
8508
|
})).mutation(async ({ input }) => {
|
|
8162
8509
|
let scenario = null;
|
|
8163
8510
|
if (input.scenarioId) {
|
|
@@ -8173,7 +8520,8 @@ var chatRouter = router({
|
|
|
8173
8520
|
input.kanbanBoardId,
|
|
8174
8521
|
input.kanbanCardId,
|
|
8175
8522
|
input.providerId,
|
|
8176
|
-
input.projectId
|
|
8523
|
+
input.projectId,
|
|
8524
|
+
input.orgId
|
|
8177
8525
|
);
|
|
8178
8526
|
if (scenario) {
|
|
8179
8527
|
await scenarioRunStore.initializeConversationWorkspace(
|
|
@@ -8400,6 +8748,7 @@ ${imagePaths.map((p) => `Image: ${p}`).join("\n")}` : cliPrompt;
|
|
|
8400
8748
|
const injectedEnv = await resolveInjectedEnv(input.environmentId);
|
|
8401
8749
|
new ClaudeCodeRunner().run({
|
|
8402
8750
|
agentId: resolvedAgentId ?? void 0,
|
|
8751
|
+
cwd: await conversationCwd(conv),
|
|
8403
8752
|
sessionId: conv.id,
|
|
8404
8753
|
prompt: promptWithImages,
|
|
8405
8754
|
isResume,
|
|
@@ -8500,6 +8849,7 @@ ${input.prompt}` : input.prompt;
|
|
|
8500
8849
|
const injectedEnv = await resolveInjectedEnv(input.environmentId);
|
|
8501
8850
|
new CodexRunner().run({
|
|
8502
8851
|
agentId: resolvedAgentId ?? void 0,
|
|
8852
|
+
cwd: await conversationCwd(conv),
|
|
8503
8853
|
sessionId: conv.codexThreadId ?? "",
|
|
8504
8854
|
prompt: cliPrompt,
|
|
8505
8855
|
isResume,
|
|
@@ -8667,6 +9017,10 @@ ${input.prompt}` : input.prompt;
|
|
|
8667
9017
|
resume: z15.boolean().default(false),
|
|
8668
9018
|
model: z15.string().max(128).optional(),
|
|
8669
9019
|
cwd: z15.string().max(4096).optional(),
|
|
9020
|
+
/** Anchors the session when no explicit cwd is given. */
|
|
9021
|
+
serviceId: z15.string().max(128).optional(),
|
|
9022
|
+
projectId: Id128Schema.optional(),
|
|
9023
|
+
orgId: z15.string().max(128).nullable().optional(),
|
|
8670
9024
|
cols: z15.number().int().min(1).max(1e3).default(120),
|
|
8671
9025
|
rows: z15.number().int().min(1).max(1e3).default(30),
|
|
8672
9026
|
enableMcp: z15.boolean().default(false),
|
|
@@ -8677,6 +9031,10 @@ ${input.prompt}` : input.prompt;
|
|
|
8677
9031
|
text: z15.string().max(1e6)
|
|
8678
9032
|
})).max(20).optional()
|
|
8679
9033
|
})).mutation(async ({ input, ctx }) => {
|
|
9034
|
+
const t0 = Date.now();
|
|
9035
|
+
const lap = (label) => {
|
|
9036
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat.pty.start] %s +%dms", label, Date.now() - t0);
|
|
9037
|
+
};
|
|
8680
9038
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
8681
9039
|
if (!agentId) throw new TRPCError11({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
8682
9040
|
if (input.sessionId) {
|
|
@@ -8693,11 +9051,13 @@ ${input.prompt}` : input.prompt;
|
|
|
8693
9051
|
rows: input.rows
|
|
8694
9052
|
}).catch(() => {
|
|
8695
9053
|
});
|
|
9054
|
+
lap("reattached to existing pty");
|
|
8696
9055
|
return { spawnId: existing.spawnId, agentId, reattached: true };
|
|
8697
9056
|
}
|
|
8698
9057
|
} catch {
|
|
8699
9058
|
}
|
|
8700
9059
|
}
|
|
9060
|
+
lap("findPtyBySession (no reattach)");
|
|
8701
9061
|
const isCodex = input.runner === "codex";
|
|
8702
9062
|
const cli = isCodex ? "codex" : "claude";
|
|
8703
9063
|
const args = [];
|
|
@@ -8737,8 +9097,10 @@ ${apiDocs}` : systemContext;
|
|
|
8737
9097
|
} catch (err) {
|
|
8738
9098
|
console.warn("[chat.pty.start] buildCliSystemContext failed:", err.message);
|
|
8739
9099
|
}
|
|
9100
|
+
lap("buildCliSystemContext");
|
|
8740
9101
|
}
|
|
8741
9102
|
const injectedEnv = await resolveInjectedEnv(input.environmentId);
|
|
9103
|
+
lap("resolveInjectedEnv");
|
|
8742
9104
|
const res = await agentManager.agentMutation(
|
|
8743
9105
|
agentId,
|
|
8744
9106
|
"ai.spawnPty",
|
|
@@ -8746,7 +9108,13 @@ ${apiDocs}` : systemContext;
|
|
|
8746
9108
|
cli,
|
|
8747
9109
|
args,
|
|
8748
9110
|
env: injectedEnv,
|
|
8749
|
-
cwd
|
|
9111
|
+
// An explicit cwd wins; otherwise the service's directory, then the
|
|
9112
|
+
// project's — never the directory the server was launched in.
|
|
9113
|
+
cwd: input.cwd ?? await resolveAgentCwd({
|
|
9114
|
+
serviceId: input.serviceId,
|
|
9115
|
+
projectId: input.projectId,
|
|
9116
|
+
orgId: input.orgId
|
|
9117
|
+
}),
|
|
8750
9118
|
cols: input.cols,
|
|
8751
9119
|
rows: input.rows,
|
|
8752
9120
|
sessionId: input.sessionId,
|
|
@@ -8755,6 +9123,7 @@ ${apiDocs}` : systemContext;
|
|
|
8755
9123
|
mcpApiToken: jwt
|
|
8756
9124
|
}
|
|
8757
9125
|
);
|
|
9126
|
+
lap("agent ai.spawnPty");
|
|
8758
9127
|
const contexts = input.attachedContexts ?? [];
|
|
8759
9128
|
if (contexts.length > 0) {
|
|
8760
9129
|
const body = contexts.map((c) => `### ${c.label}
|
|
@@ -8821,25 +9190,31 @@ Context:
|
|
|
8821
9190
|
})).subscription(async function* ({ input, signal }) {
|
|
8822
9191
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
8823
9192
|
if (!agentId) throw new TRPCError11({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
9193
|
+
const tSub = Date.now();
|
|
9194
|
+
let firstChunk = true;
|
|
8824
9195
|
const queue = [];
|
|
8825
|
-
let
|
|
9196
|
+
let resolve7 = null;
|
|
8826
9197
|
let stopped = false;
|
|
8827
9198
|
const unsub = agentManager.agentSubscribe(
|
|
8828
9199
|
agentId,
|
|
8829
9200
|
"ai.stream",
|
|
8830
9201
|
{ spawnId: input.spawnId, fromOffset: input.fromOffset },
|
|
8831
9202
|
(data) => {
|
|
9203
|
+
if (firstChunk) {
|
|
9204
|
+
firstChunk = false;
|
|
9205
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat.pty.stream] first chunk +%dms", Date.now() - tSub);
|
|
9206
|
+
}
|
|
8832
9207
|
queue.push(data);
|
|
8833
|
-
if (
|
|
8834
|
-
|
|
8835
|
-
|
|
9208
|
+
if (resolve7) {
|
|
9209
|
+
resolve7();
|
|
9210
|
+
resolve7 = null;
|
|
8836
9211
|
}
|
|
8837
9212
|
},
|
|
8838
9213
|
() => {
|
|
8839
9214
|
stopped = true;
|
|
8840
|
-
if (
|
|
8841
|
-
|
|
8842
|
-
|
|
9215
|
+
if (resolve7) {
|
|
9216
|
+
resolve7();
|
|
9217
|
+
resolve7 = null;
|
|
8843
9218
|
}
|
|
8844
9219
|
}
|
|
8845
9220
|
);
|
|
@@ -8855,7 +9230,7 @@ Context:
|
|
|
8855
9230
|
}
|
|
8856
9231
|
if (stopped) break;
|
|
8857
9232
|
await new Promise((r) => {
|
|
8858
|
-
|
|
9233
|
+
resolve7 = r;
|
|
8859
9234
|
});
|
|
8860
9235
|
}
|
|
8861
9236
|
} finally {
|
|
@@ -9006,7 +9381,9 @@ Context:
|
|
|
9006
9381
|
{
|
|
9007
9382
|
cli: "node",
|
|
9008
9383
|
args: [scriptPath],
|
|
9009
|
-
|
|
9384
|
+
// Was `process.cwd()` — the SERVER's directory, handed to an agent
|
|
9385
|
+
// that may not even run on this machine.
|
|
9386
|
+
cwd: await resolveWorkspaceCwd({}),
|
|
9010
9387
|
cols: input.cols,
|
|
9011
9388
|
rows: input.rows,
|
|
9012
9389
|
sessionId: stressSessionId,
|
|
@@ -9053,11 +9430,15 @@ async function resolveServiceContext(serviceId) {
|
|
|
9053
9430
|
if (!agentId) {
|
|
9054
9431
|
throw new TRPCError12({ code: "BAD_REQUEST", message: "No agent configured for this service" });
|
|
9055
9432
|
}
|
|
9056
|
-
return { agentId, cwd:
|
|
9433
|
+
return { agentId, cwd: await resolveWorkspaceCwd({ serviceId }) };
|
|
9057
9434
|
}
|
|
9058
9435
|
var gitTargetShape = {
|
|
9059
9436
|
serviceId: SERVICE_ID_SCHEMA.optional(),
|
|
9060
|
-
agentId: z16.string().max(128).optional()
|
|
9437
|
+
agentId: z16.string().max(128).optional(),
|
|
9438
|
+
/** Anchors on a project when no service is targeted (e.g. from the AI chat). */
|
|
9439
|
+
projectId: z16.string().max(128).optional(),
|
|
9440
|
+
/** Son organisation : un id de projet seul peut en nommer un par organisation. */
|
|
9441
|
+
orgId: z16.string().max(128).nullable().optional()
|
|
9061
9442
|
};
|
|
9062
9443
|
async function resolveGitContext(input) {
|
|
9063
9444
|
if (input.serviceId) return resolveServiceContext(input.serviceId);
|
|
@@ -9065,7 +9446,8 @@ async function resolveGitContext(input) {
|
|
|
9065
9446
|
if (!agentId) {
|
|
9066
9447
|
throw new TRPCError12({ code: "BAD_REQUEST", message: "No agent available" });
|
|
9067
9448
|
}
|
|
9068
|
-
|
|
9449
|
+
const cwd = await resolveAgentCwd({ projectId: input.projectId, orgId: input.orgId }) ?? ".";
|
|
9450
|
+
return { agentId, cwd };
|
|
9069
9451
|
}
|
|
9070
9452
|
var gitRouter = router({
|
|
9071
9453
|
/** Get full git status */
|
|
@@ -9226,7 +9608,7 @@ Rules:
|
|
|
9226
9608
|
onStatusChange: protectedProcedure.input(z16.object({ ...gitTargetShape })).subscription(async function* ({ input }) {
|
|
9227
9609
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
9228
9610
|
const queue = [];
|
|
9229
|
-
let
|
|
9611
|
+
let resolve7 = null;
|
|
9230
9612
|
let done = false;
|
|
9231
9613
|
let unsubscribe;
|
|
9232
9614
|
try {
|
|
@@ -9236,16 +9618,16 @@ Rules:
|
|
|
9236
9618
|
{ cwd },
|
|
9237
9619
|
(data) => {
|
|
9238
9620
|
queue.push(data);
|
|
9239
|
-
if (
|
|
9240
|
-
|
|
9241
|
-
|
|
9621
|
+
if (resolve7) {
|
|
9622
|
+
resolve7();
|
|
9623
|
+
resolve7 = null;
|
|
9242
9624
|
}
|
|
9243
9625
|
},
|
|
9244
9626
|
() => {
|
|
9245
9627
|
done = true;
|
|
9246
|
-
if (
|
|
9247
|
-
|
|
9248
|
-
|
|
9628
|
+
if (resolve7) {
|
|
9629
|
+
resolve7();
|
|
9630
|
+
resolve7 = null;
|
|
9249
9631
|
}
|
|
9250
9632
|
}
|
|
9251
9633
|
);
|
|
@@ -9261,7 +9643,7 @@ Rules:
|
|
|
9261
9643
|
yield queue.shift();
|
|
9262
9644
|
} else {
|
|
9263
9645
|
await new Promise((r) => {
|
|
9264
|
-
|
|
9646
|
+
resolve7 = r;
|
|
9265
9647
|
});
|
|
9266
9648
|
}
|
|
9267
9649
|
}
|
|
@@ -9393,9 +9775,9 @@ async function* streamWithActiveProvider(opts) {
|
|
|
9393
9775
|
}
|
|
9394
9776
|
|
|
9395
9777
|
// ../server/src/services/scenario-runner.ts
|
|
9396
|
-
import { mkdir as
|
|
9778
|
+
import { mkdir as mkdir14 } from "fs/promises";
|
|
9397
9779
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
9398
|
-
import { join as
|
|
9780
|
+
import { join as join14 } from "path";
|
|
9399
9781
|
|
|
9400
9782
|
// ../server/src/services/scenario-presets.ts
|
|
9401
9783
|
var BUILT_IN_PRESETS = [
|
|
@@ -9609,21 +9991,21 @@ async function* tapBus(conversationId, signal) {
|
|
|
9609
9991
|
}
|
|
9610
9992
|
if (bus.done) return;
|
|
9611
9993
|
if (signal.aborted) return;
|
|
9612
|
-
await new Promise((
|
|
9994
|
+
await new Promise((resolve7) => {
|
|
9613
9995
|
const onAbort = () => {
|
|
9614
9996
|
bus.listeners.delete(notify);
|
|
9615
|
-
|
|
9997
|
+
resolve7();
|
|
9616
9998
|
};
|
|
9617
9999
|
const notify = () => {
|
|
9618
10000
|
signal.removeEventListener("abort", onAbort);
|
|
9619
|
-
|
|
10001
|
+
resolve7();
|
|
9620
10002
|
};
|
|
9621
10003
|
bus.listeners.add(notify);
|
|
9622
10004
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
9623
10005
|
if (cursor < bus.baseIndex + bus.buffer.length || bus.done || signal.aborted) {
|
|
9624
10006
|
bus.listeners.delete(notify);
|
|
9625
10007
|
signal.removeEventListener("abort", onAbort);
|
|
9626
|
-
|
|
10008
|
+
resolve7();
|
|
9627
10009
|
}
|
|
9628
10010
|
});
|
|
9629
10011
|
}
|
|
@@ -10091,6 +10473,10 @@ async function runNodeCall(conversationId, node, systemContext, userMessage, run
|
|
|
10091
10473
|
const turnStart = Date.now();
|
|
10092
10474
|
const result = await state.runner.run({
|
|
10093
10475
|
agentId: runOverrides?.agentId ?? agentManager.getDefaultAgentId() ?? void 0,
|
|
10476
|
+
// Without this the CLI would run wherever the server was launched, whatever
|
|
10477
|
+
// project the scenario belongs to. No project: left unset, so the runner
|
|
10478
|
+
// keeps its own default rather than being sent to this machine's path.
|
|
10479
|
+
cwd: await resolveAgentCwd({ projectId: runOverrides?.projectId, orgId: runOverrides?.orgId }),
|
|
10094
10480
|
sessionId: state.sessionId,
|
|
10095
10481
|
prompt: currentPrompt,
|
|
10096
10482
|
isResume: state.isResume,
|
|
@@ -10127,9 +10513,9 @@ async function runNodeCall(conversationId, node, systemContext, userMessage, run
|
|
|
10127
10513
|
publish2({ type: "node_waiting_input", nodeId: node.id, question: result.tokenMessage });
|
|
10128
10514
|
await readme?.(`- \u2753 [${formatTs()}] **${node.label}** \u2014 waiting for user input
|
|
10129
10515
|
`);
|
|
10130
|
-
const answer = await new Promise((
|
|
10516
|
+
const answer = await new Promise((resolve7, reject) => {
|
|
10131
10517
|
const key = pendingNodeKey(conversationId, node.id);
|
|
10132
|
-
pendingNodeInputs.set(key,
|
|
10518
|
+
pendingNodeInputs.set(key, resolve7);
|
|
10133
10519
|
signal.addEventListener("abort", () => {
|
|
10134
10520
|
pendingNodeInputs.delete(key);
|
|
10135
10521
|
reject(new Error("Aborted"));
|
|
@@ -10169,7 +10555,7 @@ async function appendNodeOutput(conversationId, node, output) {
|
|
|
10169
10555
|
if (!dir) return;
|
|
10170
10556
|
const slug = node.label.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 60) || "member";
|
|
10171
10557
|
const safeId = node.id.replace(/[^a-z0-9_-]/gi, "-").slice(0, 80);
|
|
10172
|
-
const filePath =
|
|
10558
|
+
const filePath = join14(dir, `${slug}_${safeId}.md`);
|
|
10173
10559
|
const content = `## Output
|
|
10174
10560
|
|
|
10175
10561
|
${output}
|
|
@@ -10182,9 +10568,9 @@ async function appendToReadme(conversationId, bus, text) {
|
|
|
10182
10568
|
const line = `${text}
|
|
10183
10569
|
`;
|
|
10184
10570
|
try {
|
|
10185
|
-
await
|
|
10571
|
+
await mkdir14(dir, { recursive: true });
|
|
10186
10572
|
const { appendFile: appendFile2 } = await import("fs/promises");
|
|
10187
|
-
await appendFile2(
|
|
10573
|
+
await appendFile2(join14(dir, "README.md"), line, "utf-8");
|
|
10188
10574
|
} catch {
|
|
10189
10575
|
}
|
|
10190
10576
|
publishToBus(bus, { type: "readme_append", text: line });
|
|
@@ -10204,7 +10590,7 @@ async function writeFinalMd(conversationId, bus, userRequest, content) {
|
|
|
10204
10590
|
|
|
10205
10591
|
${content}
|
|
10206
10592
|
`;
|
|
10207
|
-
const filePath =
|
|
10593
|
+
const filePath = join14(dir, "FINAL.md");
|
|
10208
10594
|
await atomicWriteSecure(filePath, fullContent);
|
|
10209
10595
|
await appendToReadme(
|
|
10210
10596
|
conversationId,
|
|
@@ -10534,9 +10920,9 @@ ${outputs.get(node.id) ?? ""}`.trim();
|
|
|
10534
10920
|
publish2({ type: "node_waiting_input", nodeId: node.id, question });
|
|
10535
10921
|
await readme(`- \u2753 [${ts()}] **${node.label || node.id}** \u2014 waiting for user input
|
|
10536
10922
|
`);
|
|
10537
|
-
const answer = await new Promise((
|
|
10923
|
+
const answer = await new Promise((resolve7, reject) => {
|
|
10538
10924
|
const key = pendingNodeKey(conversationId, node.id);
|
|
10539
|
-
pendingNodeInputs.set(key,
|
|
10925
|
+
pendingNodeInputs.set(key, resolve7);
|
|
10540
10926
|
signal.addEventListener("abort", () => {
|
|
10541
10927
|
pendingNodeInputs.delete(key);
|
|
10542
10928
|
reject(new Error("Aborted"));
|
|
@@ -10935,6 +11321,10 @@ var scenarioRouter = router({
|
|
|
10935
11321
|
runnerOverride: z17.enum(["claude-code", "codex"]).optional(),
|
|
10936
11322
|
modelOverride: Id128Schema.optional(),
|
|
10937
11323
|
agentId: Id128Schema.optional(),
|
|
11324
|
+
/** Anchors the run's working directory on the project it belongs to. */
|
|
11325
|
+
projectId: Id128Schema.optional(),
|
|
11326
|
+
/** Son organisation : sans elle, un id partagé désignerait un projet au hasard. */
|
|
11327
|
+
orgId: z17.string().max(128).nullable().optional(),
|
|
10938
11328
|
resume: z17.boolean().optional()
|
|
10939
11329
|
})).subscription(async function* ({ input, signal }) {
|
|
10940
11330
|
const scenario = await scenarioStore.get(input.scenarioId);
|
|
@@ -10948,7 +11338,9 @@ var scenarioRouter = router({
|
|
|
10948
11338
|
void runScenario(input.conversationId, input.scenarioId, userRequest, {
|
|
10949
11339
|
runnerOverride: input.runnerOverride,
|
|
10950
11340
|
modelOverride: input.modelOverride,
|
|
10951
|
-
agentId: input.agentId
|
|
11341
|
+
agentId: input.agentId,
|
|
11342
|
+
projectId: input.projectId,
|
|
11343
|
+
orgId: input.orgId
|
|
10952
11344
|
}, !!input.resume);
|
|
10953
11345
|
await new Promise((r) => setTimeout(r, 10));
|
|
10954
11346
|
}
|
|
@@ -11069,21 +11461,21 @@ async function* tapJob(job, signal) {
|
|
|
11069
11461
|
yield evt;
|
|
11070
11462
|
}
|
|
11071
11463
|
if (job.done || signal.aborted) return;
|
|
11072
|
-
await new Promise((
|
|
11464
|
+
await new Promise((resolve7) => {
|
|
11073
11465
|
const onAbort = () => {
|
|
11074
11466
|
job.listeners.delete(notify);
|
|
11075
|
-
|
|
11467
|
+
resolve7();
|
|
11076
11468
|
};
|
|
11077
11469
|
const notify = () => {
|
|
11078
11470
|
signal.removeEventListener("abort", onAbort);
|
|
11079
|
-
|
|
11471
|
+
resolve7();
|
|
11080
11472
|
};
|
|
11081
11473
|
job.listeners.add(notify);
|
|
11082
11474
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
11083
11475
|
if (cursor < job.baseIndex + job.buffer.length || job.done || signal.aborted) {
|
|
11084
11476
|
job.listeners.delete(notify);
|
|
11085
11477
|
signal.removeEventListener("abort", onAbort);
|
|
11086
|
-
|
|
11478
|
+
resolve7();
|
|
11087
11479
|
}
|
|
11088
11480
|
});
|
|
11089
11481
|
}
|
|
@@ -11355,13 +11747,13 @@ var kanbanQueueRouter = router({
|
|
|
11355
11747
|
}),
|
|
11356
11748
|
onChanged: protectedProcedure.subscription(async function* () {
|
|
11357
11749
|
const queue = [];
|
|
11358
|
-
let
|
|
11750
|
+
let resolve7 = null;
|
|
11359
11751
|
const handler = () => {
|
|
11360
11752
|
if (queue.length >= 50) queue.shift();
|
|
11361
11753
|
queue.push(kanbanQueue.getState());
|
|
11362
|
-
if (
|
|
11363
|
-
|
|
11364
|
-
|
|
11754
|
+
if (resolve7) {
|
|
11755
|
+
resolve7();
|
|
11756
|
+
resolve7 = null;
|
|
11365
11757
|
}
|
|
11366
11758
|
};
|
|
11367
11759
|
kanbanQueue.on("changed", handler);
|
|
@@ -11372,7 +11764,7 @@ var kanbanQueueRouter = router({
|
|
|
11372
11764
|
yield queue.shift();
|
|
11373
11765
|
} else {
|
|
11374
11766
|
await new Promise((r) => {
|
|
11375
|
-
|
|
11767
|
+
resolve7 = r;
|
|
11376
11768
|
});
|
|
11377
11769
|
}
|
|
11378
11770
|
}
|
|
@@ -11383,10 +11775,10 @@ var kanbanQueueRouter = router({
|
|
|
11383
11775
|
});
|
|
11384
11776
|
|
|
11385
11777
|
// ../server/src/trpc/routers/kanban.ts
|
|
11386
|
-
import { mkdir as
|
|
11778
|
+
import { mkdir as mkdir15, readdir as readdir5, rm as rm3, stat as stat4, writeFile as writeFile13 } from "fs/promises";
|
|
11387
11779
|
import { createHash } from "crypto";
|
|
11388
11780
|
import { tmpdir } from "os";
|
|
11389
|
-
import { join as
|
|
11781
|
+
import { join as join15 } from "path";
|
|
11390
11782
|
import { z as z20 } from "zod";
|
|
11391
11783
|
import { TRPCError as TRPCError17 } from "@trpc/server";
|
|
11392
11784
|
function summarizeCard(card, boardId, columnTitle) {
|
|
@@ -11437,30 +11829,30 @@ function safeDecode(value) {
|
|
|
11437
11829
|
}
|
|
11438
11830
|
var MAX_ATTACHMENT_BYTES = 55 * 1024 * 1024;
|
|
11439
11831
|
function attachmentCacheDir() {
|
|
11440
|
-
return
|
|
11832
|
+
return join15(tmpdir(), "runeya-kanban-attachments");
|
|
11441
11833
|
}
|
|
11442
11834
|
var ATTACHMENT_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
11443
11835
|
async function purgeStaleAttachments(dir, ttlMs = ATTACHMENT_CACHE_TTL_MS) {
|
|
11444
11836
|
try {
|
|
11445
11837
|
const cutoff = Date.now() - ttlMs;
|
|
11446
11838
|
for (const name of await readdir5(dir)) {
|
|
11447
|
-
const path =
|
|
11448
|
-
const info = await
|
|
11839
|
+
const path = join15(dir, name);
|
|
11840
|
+
const info = await stat4(path).catch(() => null);
|
|
11449
11841
|
if (info && info.mtimeMs < cutoff) await rm3(path, { force: true });
|
|
11450
11842
|
}
|
|
11451
11843
|
} catch {
|
|
11452
11844
|
}
|
|
11453
11845
|
}
|
|
11454
11846
|
function attachmentCacheDirs() {
|
|
11455
|
-
return [attachmentCacheDir(),
|
|
11847
|
+
return [attachmentCacheDir(), join15(machineRoot(), "kanban-attachments")];
|
|
11456
11848
|
}
|
|
11457
11849
|
async function storeAttachment(id, name, body, dirs = attachmentCacheDirs()) {
|
|
11458
11850
|
const refusals = [];
|
|
11459
11851
|
for (const dir of dirs) {
|
|
11460
11852
|
try {
|
|
11461
|
-
await
|
|
11462
|
-
const path =
|
|
11463
|
-
await
|
|
11853
|
+
await mkdir15(dir, { recursive: true });
|
|
11854
|
+
const path = join15(dir, safeLocalName(id, name));
|
|
11855
|
+
await writeFile13(path, body);
|
|
11464
11856
|
void purgeStaleAttachments(dir);
|
|
11465
11857
|
return path;
|
|
11466
11858
|
} catch (err) {
|
|
@@ -11647,13 +12039,28 @@ var terminalRouter = router({
|
|
|
11647
12039
|
create: protectedProcedure.input(z21.object({
|
|
11648
12040
|
agentId: z21.string().max(128),
|
|
11649
12041
|
cwd: z21.string().max(512).optional(),
|
|
12042
|
+
/** Anchors the terminal when no explicit cwd is given. */
|
|
12043
|
+
serviceId: z21.string().max(128).optional(),
|
|
12044
|
+
projectId: z21.string().max(128).optional(),
|
|
12045
|
+
/** L'organisation du projet : un id seul peut en nommer un par organisation. */
|
|
12046
|
+
orgId: z21.string().max(128).nullable().optional(),
|
|
11650
12047
|
cols: z21.number().int().min(1).max(512).optional(),
|
|
11651
12048
|
rows: z21.number().int().min(1).max(256).optional()
|
|
11652
12049
|
})).mutation(async ({ input }) => {
|
|
11653
12050
|
const result = await agentManager.agentMutation(
|
|
11654
12051
|
input.agentId,
|
|
11655
12052
|
"terminal.create",
|
|
11656
|
-
{
|
|
12053
|
+
{
|
|
12054
|
+
// An explicit cwd wins; otherwise the service's directory, then the
|
|
12055
|
+
// project's — never the directory the server was launched in.
|
|
12056
|
+
cwd: input.cwd ?? await resolveAgentCwd({
|
|
12057
|
+
serviceId: input.serviceId,
|
|
12058
|
+
projectId: input.projectId,
|
|
12059
|
+
orgId: input.orgId
|
|
12060
|
+
}),
|
|
12061
|
+
cols: input.cols,
|
|
12062
|
+
rows: input.rows
|
|
12063
|
+
}
|
|
11657
12064
|
);
|
|
11658
12065
|
return result;
|
|
11659
12066
|
}),
|
|
@@ -11702,7 +12109,7 @@ var terminalRouter = router({
|
|
|
11702
12109
|
sessionId: z21.string().max(128)
|
|
11703
12110
|
})).subscription(async function* ({ input }) {
|
|
11704
12111
|
const queue = [];
|
|
11705
|
-
let
|
|
12112
|
+
let resolve7 = null;
|
|
11706
12113
|
let done = false;
|
|
11707
12114
|
let unsubscribe;
|
|
11708
12115
|
try {
|
|
@@ -11712,16 +12119,16 @@ var terminalRouter = router({
|
|
|
11712
12119
|
{ sessionId: input.sessionId },
|
|
11713
12120
|
(data) => {
|
|
11714
12121
|
if (typeof data === "string") queue.push(data);
|
|
11715
|
-
if (
|
|
11716
|
-
|
|
11717
|
-
|
|
12122
|
+
if (resolve7) {
|
|
12123
|
+
resolve7();
|
|
12124
|
+
resolve7 = null;
|
|
11718
12125
|
}
|
|
11719
12126
|
},
|
|
11720
12127
|
() => {
|
|
11721
12128
|
done = true;
|
|
11722
|
-
if (
|
|
11723
|
-
|
|
11724
|
-
|
|
12129
|
+
if (resolve7) {
|
|
12130
|
+
resolve7();
|
|
12131
|
+
resolve7 = null;
|
|
11725
12132
|
}
|
|
11726
12133
|
}
|
|
11727
12134
|
);
|
|
@@ -11737,7 +12144,7 @@ var terminalRouter = router({
|
|
|
11737
12144
|
yield queue.shift();
|
|
11738
12145
|
} else {
|
|
11739
12146
|
await new Promise((r) => {
|
|
11740
|
-
|
|
12147
|
+
resolve7 = r;
|
|
11741
12148
|
});
|
|
11742
12149
|
}
|
|
11743
12150
|
}
|
|
@@ -11754,7 +12161,7 @@ var utilityRouter = router({
|
|
|
11754
12161
|
wait: protectedProcedure.input(z22.object({
|
|
11755
12162
|
ms: z22.number().int().min(0).max(MAX_WAIT_MS)
|
|
11756
12163
|
})).mutation(async ({ input }) => {
|
|
11757
|
-
await new Promise((
|
|
12164
|
+
await new Promise((resolve7) => setTimeout(resolve7, input.ms));
|
|
11758
12165
|
return { waited: input.ms };
|
|
11759
12166
|
})
|
|
11760
12167
|
});
|
|
@@ -11762,8 +12169,8 @@ var utilityRouter = router({
|
|
|
11762
12169
|
// ../server/src/trpc/routers/wiki.ts
|
|
11763
12170
|
import { z as z23 } from "zod";
|
|
11764
12171
|
import { TRPCError as TRPCError19 } from "@trpc/server";
|
|
11765
|
-
import { readFile as
|
|
11766
|
-
import { resolve as resolvePath, join as
|
|
12172
|
+
import { readFile as readFile14, readdir as readdir6, stat as stat5, realpath, writeFile as writeFile14, mkdir as mkdir16, rename as rename11, rm as rm4 } from "fs/promises";
|
|
12173
|
+
import { resolve as resolvePath, join as join16, relative, sep as sep3, dirname as dirname8 } from "path";
|
|
11767
12174
|
var SCOPE_SCHEMA = z23.union([
|
|
11768
12175
|
z23.literal("global"),
|
|
11769
12176
|
z23.string().regex(/^service:[A-Za-z0-9_-]{1,128}$/, "Invalid service scope")
|
|
@@ -11800,13 +12207,13 @@ async function resolveScope(scope) {
|
|
|
11800
12207
|
throw new TRPCError19({ code: "BAD_REQUEST", message: "Sous-chemin invalide" });
|
|
11801
12208
|
}
|
|
11802
12209
|
const dataDirAbs = resolvePath(env.DATA_DIR);
|
|
11803
|
-
await
|
|
11804
|
-
const base =
|
|
12210
|
+
await mkdir16(dataDirAbs, { recursive: true });
|
|
12211
|
+
const base = dirname8(await realpath(dataDirAbs));
|
|
11805
12212
|
const target = resolvePath(base, sub);
|
|
11806
12213
|
if (target !== base && !(target + sep3).startsWith(base + sep3)) {
|
|
11807
12214
|
throw new TRPCError19({ code: "FORBIDDEN", message: "Hors du dossier de donn\xE9es du serveur" });
|
|
11808
12215
|
}
|
|
11809
|
-
await
|
|
12216
|
+
await mkdir16(target, { recursive: true });
|
|
11810
12217
|
const root = await realpath(target);
|
|
11811
12218
|
if (root !== base && !(root + sep3).startsWith(base + sep3)) {
|
|
11812
12219
|
throw new TRPCError19({ code: "FORBIDDEN", message: "Le dossier wiki r\xE9sout hors du dossier de donn\xE9es" });
|
|
@@ -11818,7 +12225,7 @@ async function resolveScope(scope) {
|
|
|
11818
12225
|
if (!service) throw new TRPCError19({ code: "NOT_FOUND", message: "Service introuvable" });
|
|
11819
12226
|
const agentId = await resolveEffectiveAgentId(service);
|
|
11820
12227
|
if (!agentId) throw new TRPCError19({ code: "BAD_REQUEST", message: "Aucun agent configur\xE9 pour ce service" });
|
|
11821
|
-
const cwd = (service.docsPath?.trim() ||
|
|
12228
|
+
const cwd = await resolveWorkspaceCwd({ serviceId, override: service.docsPath?.trim() || void 0 });
|
|
11822
12229
|
return { mode: "agent", agentId, cwd };
|
|
11823
12230
|
}
|
|
11824
12231
|
async function walkLocal(root, dir, depth, counter) {
|
|
@@ -11833,7 +12240,7 @@ async function walkLocal(root, dir, depth, counter) {
|
|
|
11833
12240
|
for (const e of entries) {
|
|
11834
12241
|
if (counter.n >= MAX_FILES) break;
|
|
11835
12242
|
if (e.name.startsWith(".") && e.name !== ".") continue;
|
|
11836
|
-
const abs =
|
|
12243
|
+
const abs = join16(dir, e.name);
|
|
11837
12244
|
const rel = relative(root, abs).split(sep3).join("/");
|
|
11838
12245
|
if (e.isDirectory()) {
|
|
11839
12246
|
if (IGNORED_DIRS.has(e.name)) continue;
|
|
@@ -11867,7 +12274,7 @@ async function resolveSafeLocal(root, relPath, requireExist) {
|
|
|
11867
12274
|
}
|
|
11868
12275
|
return real;
|
|
11869
12276
|
}
|
|
11870
|
-
const parent =
|
|
12277
|
+
const parent = dirname8(target);
|
|
11871
12278
|
let realParent;
|
|
11872
12279
|
try {
|
|
11873
12280
|
realParent = await realpath(parent);
|
|
@@ -11898,10 +12305,10 @@ var wikiRouter = router({
|
|
|
11898
12305
|
return agentManager.agentQuery(target.agentId, "wiki.readRepoMarkdown", { cwd: target.cwd, path: input.path });
|
|
11899
12306
|
}
|
|
11900
12307
|
const file = await resolveSafeLocal(target.root, input.path, true);
|
|
11901
|
-
const s = await
|
|
12308
|
+
const s = await stat5(file);
|
|
11902
12309
|
if (!s.isFile()) throw new TRPCError19({ code: "BAD_REQUEST", message: "Pas un fichier" });
|
|
11903
12310
|
if (s.size > MAX_FILE_BYTES) throw new TRPCError19({ code: "PAYLOAD_TOO_LARGE", message: "Fichier trop volumineux" });
|
|
11904
|
-
const content = await
|
|
12311
|
+
const content = await readFile14(file, "utf-8");
|
|
11905
12312
|
return { path: input.path, content };
|
|
11906
12313
|
}),
|
|
11907
12314
|
write: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA, content: z23.string().max(5e6) })).mutation(async ({ input }) => {
|
|
@@ -11913,8 +12320,8 @@ var wikiRouter = router({
|
|
|
11913
12320
|
return agentManager.agentMutation(target.agentId, "wiki.writeMarkdown", { cwd: target.cwd, path: input.path, content: input.content });
|
|
11914
12321
|
}
|
|
11915
12322
|
const file = await resolveSafeLocal(target.root, input.path, false);
|
|
11916
|
-
await
|
|
11917
|
-
await
|
|
12323
|
+
await mkdir16(dirname8(file), { recursive: true });
|
|
12324
|
+
await writeFile14(file, input.content, "utf-8");
|
|
11918
12325
|
return { path: input.path, ok: true };
|
|
11919
12326
|
}),
|
|
11920
12327
|
createFile: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
|
|
@@ -11927,13 +12334,13 @@ var wikiRouter = router({
|
|
|
11927
12334
|
}
|
|
11928
12335
|
const file = await resolveSafeLocal(target.root, input.path, false);
|
|
11929
12336
|
try {
|
|
11930
|
-
await
|
|
12337
|
+
await stat5(file);
|
|
11931
12338
|
throw new TRPCError19({ code: "CONFLICT", message: "Fichier d\xE9j\xE0 existant" });
|
|
11932
12339
|
} catch (err) {
|
|
11933
12340
|
if (err instanceof TRPCError19) throw err;
|
|
11934
12341
|
}
|
|
11935
|
-
await
|
|
11936
|
-
await
|
|
12342
|
+
await mkdir16(dirname8(file), { recursive: true });
|
|
12343
|
+
await writeFile14(file, "", "utf-8");
|
|
11937
12344
|
return { path: input.path, ok: true };
|
|
11938
12345
|
}),
|
|
11939
12346
|
createDir: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
|
|
@@ -11942,7 +12349,7 @@ var wikiRouter = router({
|
|
|
11942
12349
|
return agentManager.agentMutation(target.agentId, "wiki.createDir", { cwd: target.cwd, path: input.path });
|
|
11943
12350
|
}
|
|
11944
12351
|
const dir = await resolveSafeLocal(target.root, input.path, false);
|
|
11945
|
-
await
|
|
12352
|
+
await mkdir16(dir, { recursive: true });
|
|
11946
12353
|
return { path: input.path, ok: true };
|
|
11947
12354
|
}),
|
|
11948
12355
|
rename: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, fromPath: REL_PATH_SCHEMA, toPath: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
|
|
@@ -11953,13 +12360,13 @@ var wikiRouter = router({
|
|
|
11953
12360
|
const from = await resolveSafeLocal(target.root, input.fromPath, true);
|
|
11954
12361
|
const to = await resolveSafeLocal(target.root, input.toPath, false);
|
|
11955
12362
|
try {
|
|
11956
|
-
await
|
|
12363
|
+
await stat5(to);
|
|
11957
12364
|
throw new TRPCError19({ code: "CONFLICT", message: "La cible existe d\xE9j\xE0" });
|
|
11958
12365
|
} catch (err) {
|
|
11959
12366
|
if (err instanceof TRPCError19) throw err;
|
|
11960
12367
|
}
|
|
11961
|
-
await
|
|
11962
|
-
await
|
|
12368
|
+
await mkdir16(dirname8(to), { recursive: true });
|
|
12369
|
+
await rename11(from, to);
|
|
11963
12370
|
return { fromPath: input.fromPath, toPath: input.toPath, ok: true };
|
|
11964
12371
|
}),
|
|
11965
12372
|
ensureRoot: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA })).mutation(async ({ input }) => {
|
|
@@ -11972,7 +12379,7 @@ var wikiRouter = router({
|
|
|
11972
12379
|
if (!service) throw new TRPCError19({ code: "NOT_FOUND", message: "Service introuvable" });
|
|
11973
12380
|
const agentId = await resolveEffectiveAgentId(service);
|
|
11974
12381
|
if (!agentId) throw new TRPCError19({ code: "BAD_REQUEST", message: "Aucun agent configur\xE9" });
|
|
11975
|
-
const cwd = (service.docsPath?.trim() ||
|
|
12382
|
+
const cwd = await resolveWorkspaceCwd({ serviceId, override: service.docsPath?.trim() || void 0 });
|
|
11976
12383
|
return agentManager.agentMutation(agentId, "wiki.ensureRoot", { cwd });
|
|
11977
12384
|
}),
|
|
11978
12385
|
delete: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
|
|
@@ -12000,18 +12407,18 @@ import { z as z24 } from "zod";
|
|
|
12000
12407
|
import { TRPCError as TRPCError20 } from "@trpc/server";
|
|
12001
12408
|
|
|
12002
12409
|
// ../server/src/services/cloud-sync-store.ts
|
|
12003
|
-
import { readFile as
|
|
12004
|
-
var
|
|
12410
|
+
import { readFile as readFile15, writeFile as writeFile15, rename as rename12, mkdir as mkdir17, chmod as chmod10 } from "fs/promises";
|
|
12411
|
+
var FILENAME6 = "cloud-sync.json";
|
|
12005
12412
|
var CloudSyncStore = class {
|
|
12006
12413
|
links = /* @__PURE__ */ new Map();
|
|
12007
12414
|
loaded = false;
|
|
12008
12415
|
/** Machine-wide: this describes the machine, not any one project. */
|
|
12009
12416
|
getFilePath() {
|
|
12010
|
-
return machineFilePath(
|
|
12417
|
+
return machineFilePath(FILENAME6);
|
|
12011
12418
|
}
|
|
12012
12419
|
/** Where older versions kept it — read only, so an older Runeya keeps working. */
|
|
12013
12420
|
getLegacyFilePath() {
|
|
12014
|
-
return legacyMachineFilePath(
|
|
12421
|
+
return legacyMachineFilePath(FILENAME6);
|
|
12015
12422
|
}
|
|
12016
12423
|
/**
|
|
12017
12424
|
* Read the machine file, falling back to the launch directory of old.
|
|
@@ -12021,9 +12428,9 @@ var CloudSyncStore = class {
|
|
|
12021
12428
|
*/
|
|
12022
12429
|
async readFromDisk() {
|
|
12023
12430
|
try {
|
|
12024
|
-
return await
|
|
12431
|
+
return await readFile15(this.getFilePath(), "utf-8");
|
|
12025
12432
|
} catch {
|
|
12026
|
-
return
|
|
12433
|
+
return readFile15(this.getLegacyFilePath(), "utf-8");
|
|
12027
12434
|
}
|
|
12028
12435
|
}
|
|
12029
12436
|
async load() {
|
|
@@ -12033,24 +12440,26 @@ var CloudSyncStore = class {
|
|
|
12033
12440
|
const data = JSON.parse(raw);
|
|
12034
12441
|
for (const l of data) {
|
|
12035
12442
|
this.links.set(l.orgId, { orgId: l.orgId, cloudUrl: l.cloudUrl });
|
|
12443
|
+
void stampOrgCloud(l.orgId, l.cloudUrl);
|
|
12036
12444
|
}
|
|
12037
12445
|
} catch {
|
|
12038
12446
|
}
|
|
12039
12447
|
this.loaded = true;
|
|
12040
12448
|
}
|
|
12041
12449
|
async save() {
|
|
12042
|
-
await
|
|
12450
|
+
await mkdir17(machineRoot(), { recursive: true });
|
|
12043
12451
|
const filePath = this.getFilePath();
|
|
12044
12452
|
const tmpPath = filePath + ".tmp";
|
|
12045
12453
|
const data = JSON.stringify(Array.from(this.links.values()), null, 2);
|
|
12046
|
-
await
|
|
12047
|
-
await
|
|
12048
|
-
await
|
|
12454
|
+
await writeFile15(tmpPath, data, "utf-8");
|
|
12455
|
+
await rename12(tmpPath, filePath);
|
|
12456
|
+
await chmod10(filePath, 384);
|
|
12049
12457
|
}
|
|
12050
12458
|
/** Store (or replace) the sync link for an organization. */
|
|
12051
12459
|
async upsert(orgId, cloudUrl) {
|
|
12052
12460
|
await this.load();
|
|
12053
12461
|
this.links.set(orgId, { orgId, cloudUrl });
|
|
12462
|
+
await stampOrgCloud(orgId, cloudUrl);
|
|
12054
12463
|
await this.save();
|
|
12055
12464
|
}
|
|
12056
12465
|
async remove(orgId) {
|
|
@@ -12071,16 +12480,16 @@ var cloudSyncStore = new CloudSyncStore();
|
|
|
12071
12480
|
|
|
12072
12481
|
// ../server/src/services/cloud-sync-bridge.ts
|
|
12073
12482
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
12074
|
-
import { mkdir as
|
|
12483
|
+
import { mkdir as mkdir19, rm as rm5 } from "fs/promises";
|
|
12075
12484
|
import * as Y from "yjs";
|
|
12076
12485
|
|
|
12077
12486
|
// ../server/src/services/org-lock.ts
|
|
12078
|
-
import { open as open2, readFile as
|
|
12487
|
+
import { open as open2, readFile as readFile16, unlink as unlink4, mkdir as mkdir18 } from "fs/promises";
|
|
12079
12488
|
import { readFileSync, unlinkSync } from "fs";
|
|
12080
|
-
import { join as
|
|
12489
|
+
import { join as join17 } from "path";
|
|
12081
12490
|
var held = /* @__PURE__ */ new Set();
|
|
12082
12491
|
function lockPath(orgId) {
|
|
12083
|
-
return
|
|
12492
|
+
return join17(orgVaultRoot(orgId), "org.lock");
|
|
12084
12493
|
}
|
|
12085
12494
|
function isRunning(pid) {
|
|
12086
12495
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -12093,7 +12502,7 @@ function isRunning(pid) {
|
|
|
12093
12502
|
}
|
|
12094
12503
|
async function readLock(path) {
|
|
12095
12504
|
try {
|
|
12096
|
-
const parsed = JSON.parse(await
|
|
12505
|
+
const parsed = JSON.parse(await readFile16(path, "utf-8"));
|
|
12097
12506
|
return typeof parsed?.pid === "number" ? parsed : null;
|
|
12098
12507
|
} catch {
|
|
12099
12508
|
return null;
|
|
@@ -12101,7 +12510,7 @@ async function readLock(path) {
|
|
|
12101
12510
|
}
|
|
12102
12511
|
async function acquireOrgLock(orgId) {
|
|
12103
12512
|
const path = lockPath(orgId);
|
|
12104
|
-
await
|
|
12513
|
+
await mkdir18(orgVaultRoot(orgId), { recursive: true });
|
|
12105
12514
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
12106
12515
|
try {
|
|
12107
12516
|
const handle = await open2(path, "wx");
|
|
@@ -12168,6 +12577,17 @@ var SyncOutdatedError = class extends Error {
|
|
|
12168
12577
|
this.lastIncompatibleVersion = lastIncompatibleVersion;
|
|
12169
12578
|
}
|
|
12170
12579
|
};
|
|
12580
|
+
var ProjectHeldElsewhereError = class extends Error {
|
|
12581
|
+
constructor(projectId, heldByOrgId) {
|
|
12582
|
+
super(
|
|
12583
|
+
`Project ${projectId} is already held here for ${heldByOrgId ? `organization ${heldByOrgId}` : "this directory"}. Two copies of one project share every entity id \u2014 stop following the other one first.`
|
|
12584
|
+
);
|
|
12585
|
+
this.projectId = projectId;
|
|
12586
|
+
this.heldByOrgId = heldByOrgId;
|
|
12587
|
+
this.name = "ProjectHeldElsewhereError";
|
|
12588
|
+
}
|
|
12589
|
+
code = "PROJECT_HELD_ELSEWHERE";
|
|
12590
|
+
};
|
|
12171
12591
|
var OrgSyncConnection = class {
|
|
12172
12592
|
doc = new Y.Doc();
|
|
12173
12593
|
projects = getCollection(this.doc, "projects");
|
|
@@ -12484,18 +12904,24 @@ var OrgSyncConnection = class {
|
|
|
12484
12904
|
*/
|
|
12485
12905
|
async followProject(projectId) {
|
|
12486
12906
|
const inDoc = readEntity(this.collection("projects"), projectId) !== null;
|
|
12487
|
-
const existing = await projectStore.get(projectId);
|
|
12907
|
+
const existing = await projectStore.get({ id: projectId, orgId: this.link.orgId }) ?? await projectStore.get({ id: projectId, orgId: null });
|
|
12488
12908
|
if (existing && !existing.orgId) {
|
|
12489
12909
|
console.error(
|
|
12490
12910
|
"[cloud-sync] project %s is already a local project here \u2014 not following it for org %s",
|
|
12491
12911
|
projectId,
|
|
12492
12912
|
this.link.orgId
|
|
12493
12913
|
);
|
|
12494
|
-
|
|
12914
|
+
throw new ProjectHeldElsewhereError(projectId, null);
|
|
12915
|
+
}
|
|
12916
|
+
const heldBy = await projectStore.list().then((all) => all.find(
|
|
12917
|
+
(p) => p.id === projectId && p.orgId && p.orgId !== this.link.orgId
|
|
12918
|
+
));
|
|
12919
|
+
if (heldBy?.orgId) {
|
|
12920
|
+
throw new ProjectHeldElsewhereError(projectId, heldBy.orgId);
|
|
12495
12921
|
}
|
|
12496
12922
|
const isLocal = existing?.orgId === this.link.orgId;
|
|
12497
12923
|
if (!inDoc && !isLocal) return false;
|
|
12498
|
-
await
|
|
12924
|
+
await mkdir19(projectVaultRoot(this.link.orgId, projectId), { recursive: true });
|
|
12499
12925
|
this.tracked.add(projectId);
|
|
12500
12926
|
if (inDoc) await this.pullFromDoc([projectId]);
|
|
12501
12927
|
else await this.reconcileLocalToDoc();
|
|
@@ -12510,7 +12936,7 @@ var OrgSyncConnection = class {
|
|
|
12510
12936
|
*/
|
|
12511
12937
|
async unfollowProject(projectId) {
|
|
12512
12938
|
this.tracked.delete(projectId);
|
|
12513
|
-
await projectStore.forgetLocally(projectId);
|
|
12939
|
+
await projectStore.forgetLocally({ id: projectId, orgId: this.link.orgId });
|
|
12514
12940
|
await rm5(projectVaultRoot(this.link.orgId, projectId), { recursive: true, force: true }).catch(() => {
|
|
12515
12941
|
});
|
|
12516
12942
|
}
|
|
@@ -12592,9 +13018,33 @@ var OrgSyncConnection = class {
|
|
|
12592
13018
|
if (result?.created) await this.bootstrapEnvironment(id);
|
|
12593
13019
|
}
|
|
12594
13020
|
async deleteLocally(name, id) {
|
|
12595
|
-
if (name === "services")
|
|
12596
|
-
|
|
12597
|
-
|
|
13021
|
+
if (name === "services") {
|
|
13022
|
+
await serviceStore.applyRemoteDelete(id);
|
|
13023
|
+
return;
|
|
13024
|
+
}
|
|
13025
|
+
if (name === "environments") {
|
|
13026
|
+
await environmentStore.applyRemoteDelete(id);
|
|
13027
|
+
return;
|
|
13028
|
+
}
|
|
13029
|
+
const refusal = this.refusalFor(id);
|
|
13030
|
+
if (refusal) {
|
|
13031
|
+
console.error(
|
|
13032
|
+
"[cloud-sync] project %s refused by the cloud for org %s (%s) \u2014 kept on disk, no longer pushed",
|
|
13033
|
+
id,
|
|
13034
|
+
this.link.orgId,
|
|
13035
|
+
refusal
|
|
13036
|
+
);
|
|
13037
|
+
this.tracked.delete(id);
|
|
13038
|
+
return;
|
|
13039
|
+
}
|
|
13040
|
+
await projectStore.applyRemoteDelete({ id, orgId: this.link.orgId });
|
|
13041
|
+
}
|
|
13042
|
+
/** Le motif du refus du cloud pour ce projet, s'il en a écrit un. */
|
|
13043
|
+
refusalFor(projectId) {
|
|
13044
|
+
const entry = this.doc.getMap("projectRefusals").get(projectId);
|
|
13045
|
+
if (!entry || typeof entry !== "object") return null;
|
|
13046
|
+
const reason = entry.reason;
|
|
13047
|
+
return typeof reason === "string" ? reason : "refused";
|
|
12598
13048
|
}
|
|
12599
13049
|
async bootstrapEnvironment(projectId) {
|
|
12600
13050
|
try {
|
|
@@ -12606,8 +13056,8 @@ var OrgSyncConnection = class {
|
|
|
12606
13056
|
description: "Default local environment (immutable)",
|
|
12607
13057
|
parentIds: [],
|
|
12608
13058
|
immutable: true
|
|
12609
|
-
});
|
|
12610
|
-
await projectStore.addEnvironment(projectId, localEnv.id);
|
|
13059
|
+
}, this.link.orgId);
|
|
13060
|
+
await projectStore.addEnvironment({ id: projectId, orgId: this.link.orgId }, localEnv.id);
|
|
12611
13061
|
} catch (err) {
|
|
12612
13062
|
console.error("[cloud-sync] env bootstrap failed for project %s:", projectId, err.message);
|
|
12613
13063
|
}
|
|
@@ -12630,7 +13080,7 @@ var OrgSyncConnection = class {
|
|
|
12630
13080
|
*/
|
|
12631
13081
|
async reconcileLocalToDoc() {
|
|
12632
13082
|
if (this.stopped || this.pulling) return;
|
|
12633
|
-
const projects = await projectStore.listDiskFormByOrg(this.link.orgId);
|
|
13083
|
+
const projects = (await projectStore.listDiskFormByOrg(this.link.orgId)).filter((p) => !this.refusalFor(p.id));
|
|
12634
13084
|
const projectIds = projects.map((p) => p.id);
|
|
12635
13085
|
const serviceIds = [...new Set(projects.flatMap((p) => p.serviceIds ?? []))];
|
|
12636
13086
|
const [services, environments] = await Promise.all([
|
|
@@ -12780,8 +13230,8 @@ var cloudSyncBridge = new CloudSyncBridge();
|
|
|
12780
13230
|
|
|
12781
13231
|
// ../server/src/services/master-password.ts
|
|
12782
13232
|
import { randomBytes as randomBytes4, createCipheriv, createDecipheriv } from "crypto";
|
|
12783
|
-
import { readFile as
|
|
12784
|
-
import { join as
|
|
13233
|
+
import { readFile as readFile17, writeFile as writeFile16, chmod as chmod11, stat as stat6, rename as rename13 } from "fs/promises";
|
|
13234
|
+
import { join as join18 } from "path";
|
|
12785
13235
|
import { Entry } from "@napi-rs/keyring";
|
|
12786
13236
|
import { hashRaw as argon2HashRaw } from "@node-rs/argon2";
|
|
12787
13237
|
var KEYRING_SERVICE = "runeya";
|
|
@@ -12793,9 +13243,9 @@ var CANARY_PLAINTEXT = "runeya:v1:key-verification";
|
|
|
12793
13243
|
var MAX_KEY_FILE_SIZE = 1024;
|
|
12794
13244
|
var ARGON2_OPTIONS = { memoryCost: 65536, timeCost: 3, parallelism: 4, outputLen: 32 };
|
|
12795
13245
|
async function readKeyFile(filePath) {
|
|
12796
|
-
const s = await
|
|
13246
|
+
const s = await stat6(filePath);
|
|
12797
13247
|
if (s.size > MAX_KEY_FILE_SIZE) throw new Error(`Fichier suspect : ${filePath} (${s.size} octets)`);
|
|
12798
|
-
return
|
|
13248
|
+
return readFile17(filePath);
|
|
12799
13249
|
}
|
|
12800
13250
|
function aesEncrypt(key, plaintext) {
|
|
12801
13251
|
const iv = randomBytes4(12);
|
|
@@ -12814,7 +13264,7 @@ function aesDecrypt(key, data) {
|
|
|
12814
13264
|
}
|
|
12815
13265
|
async function getPepperFromFile(dataDir) {
|
|
12816
13266
|
try {
|
|
12817
|
-
const raw = await readKeyFile(
|
|
13267
|
+
const raw = await readKeyFile(join18(dataDir, PEPPER_FILENAME));
|
|
12818
13268
|
const hex = raw.toString("utf-8").trim();
|
|
12819
13269
|
if (/^[0-9a-f]{64}$/i.test(hex)) return Buffer.from(hex, "hex");
|
|
12820
13270
|
} catch {
|
|
@@ -12822,11 +13272,11 @@ async function getPepperFromFile(dataDir) {
|
|
|
12822
13272
|
return null;
|
|
12823
13273
|
}
|
|
12824
13274
|
async function writePepperToFile(dataDir, pepper) {
|
|
12825
|
-
const filePath =
|
|
13275
|
+
const filePath = join18(dataDir, PEPPER_FILENAME);
|
|
12826
13276
|
const tmpPath = filePath + ".tmp";
|
|
12827
|
-
await
|
|
12828
|
-
await
|
|
12829
|
-
await
|
|
13277
|
+
await writeFile16(tmpPath, pepper.toString("hex"), "utf-8");
|
|
13278
|
+
await chmod11(tmpPath, 384);
|
|
13279
|
+
await rename13(tmpPath, filePath);
|
|
12830
13280
|
}
|
|
12831
13281
|
function tryKeyringGet() {
|
|
12832
13282
|
try {
|
|
@@ -12873,14 +13323,14 @@ async function writeEncryptedKey(dataDir, masterPassword, encryptionKey) {
|
|
|
12873
13323
|
const payload = aesEncrypt(derivedKey, encryptionKey);
|
|
12874
13324
|
const fileContent = Buffer.concat([salt, payload]);
|
|
12875
13325
|
const encoded = fileContent.toString("base64");
|
|
12876
|
-
const filePath =
|
|
13326
|
+
const filePath = join18(dataDir, ENCRYPTED_KEY_FILENAME);
|
|
12877
13327
|
const tmpPath = filePath + ".tmp";
|
|
12878
|
-
await
|
|
12879
|
-
await
|
|
12880
|
-
await
|
|
13328
|
+
await writeFile16(tmpPath, encoded, "utf-8");
|
|
13329
|
+
await chmod11(tmpPath, 384);
|
|
13330
|
+
await rename13(tmpPath, filePath);
|
|
12881
13331
|
}
|
|
12882
13332
|
async function readEncryptedKey(dataDir, masterPassword) {
|
|
12883
|
-
const filePath =
|
|
13333
|
+
const filePath = join18(dataDir, ENCRYPTED_KEY_FILENAME);
|
|
12884
13334
|
const raw = await readKeyFile(filePath);
|
|
12885
13335
|
const fileContent = Buffer.from(raw.toString("utf-8").trim(), "base64");
|
|
12886
13336
|
const salt = fileContent.subarray(0, 16);
|
|
@@ -12900,13 +13350,13 @@ async function getPepperFromKeychain(dataDir) {
|
|
|
12900
13350
|
}
|
|
12901
13351
|
async function writeVerifyFile(dataDir, encryptionKey) {
|
|
12902
13352
|
const payload = aesEncrypt(encryptionKey, Buffer.from(CANARY_PLAINTEXT, "utf-8"));
|
|
12903
|
-
const filePath =
|
|
13353
|
+
const filePath = join18(dataDir, VERIFY_FILENAME);
|
|
12904
13354
|
const tmpPath = filePath + ".tmp";
|
|
12905
|
-
await
|
|
12906
|
-
await
|
|
13355
|
+
await writeFile16(tmpPath, payload.toString("base64"), "utf-8");
|
|
13356
|
+
await rename13(tmpPath, filePath);
|
|
12907
13357
|
}
|
|
12908
13358
|
async function verifyEncryptionKey(dataDir, encryptionKey) {
|
|
12909
|
-
const filePath =
|
|
13359
|
+
const filePath = join18(dataDir, VERIFY_FILENAME);
|
|
12910
13360
|
const raw = await readKeyFile(filePath);
|
|
12911
13361
|
if (!matchesCanary(raw.toString("utf-8"), encryptionKey)) {
|
|
12912
13362
|
throw new Error("Cl\xE9 d'encryption incorrecte");
|
|
@@ -12922,14 +13372,14 @@ function matchesCanary(canaryBase64, encryptionKey) {
|
|
|
12922
13372
|
}
|
|
12923
13373
|
async function readVerifyFile(dataDir) {
|
|
12924
13374
|
try {
|
|
12925
|
-
return (await readKeyFile(
|
|
13375
|
+
return (await readKeyFile(join18(dataDir, VERIFY_FILENAME))).toString("utf-8").trim();
|
|
12926
13376
|
} catch {
|
|
12927
13377
|
return null;
|
|
12928
13378
|
}
|
|
12929
13379
|
}
|
|
12930
13380
|
async function hasVerifyFile(dataDir) {
|
|
12931
13381
|
try {
|
|
12932
|
-
await
|
|
13382
|
+
await stat6(join18(dataDir, VERIFY_FILENAME));
|
|
12933
13383
|
return true;
|
|
12934
13384
|
} catch {
|
|
12935
13385
|
return false;
|
|
@@ -12937,7 +13387,7 @@ async function hasVerifyFile(dataDir) {
|
|
|
12937
13387
|
}
|
|
12938
13388
|
async function hasEncryptedKeyFile(dataDir) {
|
|
12939
13389
|
try {
|
|
12940
|
-
await
|
|
13390
|
+
await stat6(join18(dataDir, ENCRYPTED_KEY_FILENAME));
|
|
12941
13391
|
return true;
|
|
12942
13392
|
} catch {
|
|
12943
13393
|
return false;
|
|
@@ -12945,6 +13395,20 @@ async function hasEncryptedKeyFile(dataDir) {
|
|
|
12945
13395
|
}
|
|
12946
13396
|
|
|
12947
13397
|
// ../server/src/trpc/routers/cloud-sync.ts
|
|
13398
|
+
async function cloudMessage(res) {
|
|
13399
|
+
try {
|
|
13400
|
+
const body = await res.text();
|
|
13401
|
+
if (!body) return "";
|
|
13402
|
+
try {
|
|
13403
|
+
const parsed = JSON.parse(body);
|
|
13404
|
+
if (typeof parsed.message === "string") return parsed.message;
|
|
13405
|
+
} catch {
|
|
13406
|
+
}
|
|
13407
|
+
return body.slice(0, 300);
|
|
13408
|
+
} catch {
|
|
13409
|
+
return "";
|
|
13410
|
+
}
|
|
13411
|
+
}
|
|
12948
13412
|
async function reconcileOrgCanary(orgId) {
|
|
12949
13413
|
const key = getEncryptionKey();
|
|
12950
13414
|
if (!key) {
|
|
@@ -13019,8 +13483,15 @@ var cloudSyncRouter = router({
|
|
|
13019
13483
|
const link = await cloudSyncStore.get(input.orgId);
|
|
13020
13484
|
if (link) await cloudSyncBridge.connect(link);
|
|
13021
13485
|
}
|
|
13022
|
-
|
|
13023
|
-
|
|
13486
|
+
try {
|
|
13487
|
+
const pulled = await cloudSyncBridge.followProject(input.orgId, input.projectId);
|
|
13488
|
+
return { pulled, state: syncState(input.orgId) };
|
|
13489
|
+
} catch (err) {
|
|
13490
|
+
if (err instanceof ProjectHeldElsewhereError) {
|
|
13491
|
+
throw new TRPCError20({ code: "CONFLICT", message: err.code, cause: err });
|
|
13492
|
+
}
|
|
13493
|
+
throw err;
|
|
13494
|
+
}
|
|
13024
13495
|
}),
|
|
13025
13496
|
/**
|
|
13026
13497
|
* Stop materializing one project of an org on this machine.
|
|
@@ -13037,6 +13508,47 @@ var cloudSyncRouter = router({
|
|
|
13037
13508
|
await cloudSyncBridge.unfollowProject(input.orgId, input.projectId);
|
|
13038
13509
|
return { success: true };
|
|
13039
13510
|
}),
|
|
13511
|
+
/**
|
|
13512
|
+
* Retirer un projet de l'organisation — pour tous ses membres.
|
|
13513
|
+
*
|
|
13514
|
+
* À ne pas confondre avec `untrackProject`, qui ne concerne que cette
|
|
13515
|
+
* machine. Ici le projet quitte l'organisation : il disparaît pour chaque
|
|
13516
|
+
* membre, et le cloud rend son identifiant. La suppression locale n'est pas
|
|
13517
|
+
* faite ici : elle arrive par la synchronisation, comme pour tout autre
|
|
13518
|
+
* membre, ce qui évite d'effacer les fichiers si le cloud a refusé l'ordre.
|
|
13519
|
+
*/
|
|
13520
|
+
deleteProjectFromOrg: protectedProcedure.input(
|
|
13521
|
+
z24.object({
|
|
13522
|
+
orgId: z24.string().min(1).max(128),
|
|
13523
|
+
projectId: z24.string().min(1).max(128)
|
|
13524
|
+
})
|
|
13525
|
+
).mutation(async ({ input }) => {
|
|
13526
|
+
let res;
|
|
13527
|
+
try {
|
|
13528
|
+
res = await cloudFetch("/api/auth/project/delete", {
|
|
13529
|
+
method: "POST",
|
|
13530
|
+
body: { organizationId: input.orgId, projectId: input.projectId }
|
|
13531
|
+
});
|
|
13532
|
+
} catch (err) {
|
|
13533
|
+
if (err instanceof TRPCError20 && err.code === "PRECONDITION_FAILED") {
|
|
13534
|
+
throw new TRPCError20({ code: "PRECONDITION_FAILED", message: "CLOUD_NOT_LINKED" });
|
|
13535
|
+
}
|
|
13536
|
+
throw err;
|
|
13537
|
+
}
|
|
13538
|
+
if (res.ok) return { success: true };
|
|
13539
|
+
const detail = await cloudMessage(res);
|
|
13540
|
+
if (res.status === 403) {
|
|
13541
|
+
const code = /not a member/i.test(detail) ? "PROJECT_NOT_MEMBER" : "PROJECT_DELETE_FORBIDDEN";
|
|
13542
|
+
throw new TRPCError20({ code: "FORBIDDEN", message: code, cause: new Error(detail) });
|
|
13543
|
+
}
|
|
13544
|
+
if (res.status === 404) {
|
|
13545
|
+
throw new TRPCError20({ code: "NOT_FOUND", message: "PROJECT_NOT_IN_ORG", cause: new Error(detail) });
|
|
13546
|
+
}
|
|
13547
|
+
throw new TRPCError20({
|
|
13548
|
+
code: "BAD_GATEWAY",
|
|
13549
|
+
message: detail || `PROJECT_DELETE_FAILED (${res.status})`
|
|
13550
|
+
});
|
|
13551
|
+
}),
|
|
13040
13552
|
/** Stop syncing an org and forget its token. */
|
|
13041
13553
|
unlink: protectedProcedure.input(z24.object({ orgId: z24.string().min(1).max(128) })).mutation(async ({ input }) => {
|
|
13042
13554
|
cloudSyncBridge.disconnect(input.orgId);
|
|
@@ -13059,6 +13571,104 @@ var cloudSyncRouter = router({
|
|
|
13059
13571
|
// borne est exclusive — il faut strictement plus récent que ce numéro.
|
|
13060
13572
|
lastIncompatibleVersion: states.get(l.orgId)?.lastIncompatibleVersion ?? null
|
|
13061
13573
|
}));
|
|
13574
|
+
}),
|
|
13575
|
+
/**
|
|
13576
|
+
* The cloud instance behind every org vault on this machine.
|
|
13577
|
+
*
|
|
13578
|
+
* Wider than `list`, which only sees the links of the directory this server
|
|
13579
|
+
* was launched in: org vaults are shared machine-wide, so a project can very
|
|
13580
|
+
* well belong to an org linked from another directory — or from another cloud
|
|
13581
|
+
* entirely.
|
|
13582
|
+
*/
|
|
13583
|
+
orgClouds: protectedProcedure.query(async () => {
|
|
13584
|
+
const clouds = await readOrgClouds();
|
|
13585
|
+
for (const l of await cloudSyncStore.list()) clouds[l.orgId] = l.cloudUrl;
|
|
13586
|
+
return clouds;
|
|
13587
|
+
})
|
|
13588
|
+
});
|
|
13589
|
+
|
|
13590
|
+
// ../server/src/trpc/routers/filesystem.ts
|
|
13591
|
+
import { readdir as readdir7, stat as stat7 } from "fs/promises";
|
|
13592
|
+
import { homedir as homedir5 } from "os";
|
|
13593
|
+
import { isAbsolute as isAbsolute4, join as join19, resolve as resolve5, dirname as dirname9, basename as basename2 } from "path";
|
|
13594
|
+
import { z as z25 } from "zod";
|
|
13595
|
+
var MAX_ENTRIES = 500;
|
|
13596
|
+
var PATH_SCHEMA = z25.string().min(1).max(4096);
|
|
13597
|
+
function safeResolve(input) {
|
|
13598
|
+
if (input.includes("\0")) throw new Error("Chemin invalide");
|
|
13599
|
+
const expanded = input.startsWith("~") ? join19(homedir5(), input.slice(1)) : input;
|
|
13600
|
+
if (!isAbsolute4(expanded)) throw new Error("Le chemin doit \xEAtre absolu");
|
|
13601
|
+
return resolve5(expanded);
|
|
13602
|
+
}
|
|
13603
|
+
var filesystemRouter = router({
|
|
13604
|
+
/** Where a picker should open when the user has chosen nothing yet. */
|
|
13605
|
+
suggestedRoots: protectedProcedure.query(async () => {
|
|
13606
|
+
const roots = [
|
|
13607
|
+
{ label: "home", path: homedir5() },
|
|
13608
|
+
// The launch directory, taken from the vault rather than the process.
|
|
13609
|
+
{ label: "cwd", path: await resolveWorkspaceCwd({}) }
|
|
13610
|
+
];
|
|
13611
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13612
|
+
return roots.filter((r) => !seen.has(r.path) && seen.add(r.path));
|
|
13613
|
+
}),
|
|
13614
|
+
/**
|
|
13615
|
+
* The sub-directories of one directory.
|
|
13616
|
+
*
|
|
13617
|
+
* Hidden entries are skipped: they are noise for choosing a workspace, and
|
|
13618
|
+
* `.runeya` itself would invite placing a project inside another's data.
|
|
13619
|
+
*/
|
|
13620
|
+
listDirectories: protectedProcedure.input(z25.object({ path: PATH_SCHEMA })).query(async ({ input }) => {
|
|
13621
|
+
const dir = safeResolve(input.path);
|
|
13622
|
+
const info = await stat7(dir).catch(() => null);
|
|
13623
|
+
if (!info?.isDirectory()) {
|
|
13624
|
+
throw new Error(`Ce n'est pas un r\xE9pertoire : ${dir}`);
|
|
13625
|
+
}
|
|
13626
|
+
let names;
|
|
13627
|
+
try {
|
|
13628
|
+
names = (await readdir7(dir, { withFileTypes: true })).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort((a, b) => a.localeCompare(b));
|
|
13629
|
+
} catch {
|
|
13630
|
+
throw new Error(`R\xE9pertoire illisible : ${dir}`);
|
|
13631
|
+
}
|
|
13632
|
+
const truncated = names.length > MAX_ENTRIES;
|
|
13633
|
+
const entries = [];
|
|
13634
|
+
for (const name of names.slice(0, MAX_ENTRIES)) {
|
|
13635
|
+
const path = join19(dir, name);
|
|
13636
|
+
entries.push({ name, path, hasRuneyaProject: await holdsRuneyaProject(path) });
|
|
13637
|
+
}
|
|
13638
|
+
return {
|
|
13639
|
+
path: dir,
|
|
13640
|
+
// Null at the filesystem root, which is what stops "up" from looping.
|
|
13641
|
+
parent: dirname9(dir) === dir ? null : dirname9(dir),
|
|
13642
|
+
name: basename2(dir) || dir,
|
|
13643
|
+
entries,
|
|
13644
|
+
truncated,
|
|
13645
|
+
hasRuneyaProject: await holdsRuneyaProject(dir)
|
|
13646
|
+
};
|
|
13647
|
+
}),
|
|
13648
|
+
/**
|
|
13649
|
+
* Whether a directory can receive this project.
|
|
13650
|
+
*
|
|
13651
|
+
* Two projects in one directory is what makes services run against the wrong
|
|
13652
|
+
* code, so a directory that already holds one is refused outright.
|
|
13653
|
+
*/
|
|
13654
|
+
checkProjectTarget: protectedProcedure.input(z25.object({
|
|
13655
|
+
path: PATH_SCHEMA,
|
|
13656
|
+
projectId: z25.string().min(1).max(128),
|
|
13657
|
+
orgId: z25.string().max(128).nullable().optional()
|
|
13658
|
+
})).query(async ({ input }) => {
|
|
13659
|
+
const dir = safeResolve(input.path);
|
|
13660
|
+
const info = await stat7(dir).catch(() => null);
|
|
13661
|
+
if (!info?.isDirectory()) {
|
|
13662
|
+
return { ok: false, reason: "missing", path: dir, otherProjectId: null };
|
|
13663
|
+
}
|
|
13664
|
+
if (await holdsRuneyaProject(dir)) {
|
|
13665
|
+
return { ok: false, reason: "has-runeya-project", path: dir, otherProjectId: null };
|
|
13666
|
+
}
|
|
13667
|
+
const otherProjectId = await projectPathStore.projectAt(dir, { id: input.projectId, orgId: input.orgId });
|
|
13668
|
+
if (otherProjectId) {
|
|
13669
|
+
return { ok: false, reason: "taken", path: dir, otherProjectId };
|
|
13670
|
+
}
|
|
13671
|
+
return { ok: true, reason: null, path: dir, otherProjectId: null };
|
|
13062
13672
|
})
|
|
13063
13673
|
});
|
|
13064
13674
|
|
|
@@ -13086,7 +13696,8 @@ var appRouter = router({
|
|
|
13086
13696
|
wiki: wikiRouter,
|
|
13087
13697
|
config: configRouter,
|
|
13088
13698
|
cloudSync: cloudSyncRouter,
|
|
13089
|
-
cloudAuth: cloudAuthRouter
|
|
13699
|
+
cloudAuth: cloudAuthRouter,
|
|
13700
|
+
filesystem: filesystemRouter
|
|
13090
13701
|
});
|
|
13091
13702
|
|
|
13092
13703
|
// ../server/src/middleware/error-handler.ts
|
|
@@ -13121,7 +13732,7 @@ var MigrationEngine = class {
|
|
|
13121
13732
|
var migrationEngine = new MigrationEngine();
|
|
13122
13733
|
|
|
13123
13734
|
// ../server/src/services/file-security.ts
|
|
13124
|
-
import { chmod as
|
|
13735
|
+
import { chmod as chmod12, stat as stat8 } from "fs/promises";
|
|
13125
13736
|
var SENSITIVE_FILES = [
|
|
13126
13737
|
"agents.json",
|
|
13127
13738
|
"settings.json",
|
|
@@ -13135,11 +13746,11 @@ async function ensureFilePermissions() {
|
|
|
13135
13746
|
for (const filename of SENSITIVE_FILES) {
|
|
13136
13747
|
for (const filepath of [machineFilePath(filename), legacyMachineFilePath(filename)]) {
|
|
13137
13748
|
try {
|
|
13138
|
-
const stats = await
|
|
13749
|
+
const stats = await stat8(filepath);
|
|
13139
13750
|
const mode = stats.mode & 511;
|
|
13140
13751
|
if (mode !== SECURE_MODE) {
|
|
13141
13752
|
console.warn("[security] File", filepath, "has mode", mode.toString(8) + ", fixing to 600");
|
|
13142
|
-
await
|
|
13753
|
+
await chmod12(filepath, SECURE_MODE);
|
|
13143
13754
|
}
|
|
13144
13755
|
} catch (err) {
|
|
13145
13756
|
if (err.code !== "ENOENT") {
|
|
@@ -13151,8 +13762,8 @@ async function ensureFilePermissions() {
|
|
|
13151
13762
|
}
|
|
13152
13763
|
|
|
13153
13764
|
// ../server/src/services/vault-migration.ts
|
|
13154
|
-
import { readdir as
|
|
13155
|
-
import { join as
|
|
13765
|
+
import { readdir as readdir8, readFile as readFile18, writeFile as writeFile17, mkdir as mkdir20, copyFile, unlink as unlink5, rename as rename14, stat as stat9 } from "fs/promises";
|
|
13766
|
+
import { join as join20 } from "path";
|
|
13156
13767
|
var KEY_FILES = [".encryption.key.enc", ".runeya-verify"];
|
|
13157
13768
|
var MACHINE_FILES = [
|
|
13158
13769
|
"agents.json",
|
|
@@ -13185,12 +13796,12 @@ function rekeyValue(value, from, to, counter) {
|
|
|
13185
13796
|
return value;
|
|
13186
13797
|
}
|
|
13187
13798
|
async function vaultJsonFiles(root) {
|
|
13188
|
-
const entries = await
|
|
13799
|
+
const entries = await readdir8(root, { withFileTypes: true });
|
|
13189
13800
|
return entries.filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => e.name);
|
|
13190
13801
|
}
|
|
13191
13802
|
async function exists(path) {
|
|
13192
13803
|
try {
|
|
13193
|
-
await
|
|
13804
|
+
await stat9(path);
|
|
13194
13805
|
return true;
|
|
13195
13806
|
} catch {
|
|
13196
13807
|
return false;
|
|
@@ -13203,17 +13814,17 @@ async function openLegacyVault(legacyRoot, masterPassword) {
|
|
|
13203
13814
|
}
|
|
13204
13815
|
async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machineKey, now) {
|
|
13205
13816
|
const stamp = now.toISOString().replace(/[:.]/g, "-").substring(0, 19);
|
|
13206
|
-
const backupDir =
|
|
13207
|
-
await
|
|
13817
|
+
const backupDir = join20(legacyRoot, `backup-${stamp}`);
|
|
13818
|
+
await mkdir20(backupDir, { recursive: true });
|
|
13208
13819
|
const jsonFiles = await vaultJsonFiles(legacyRoot);
|
|
13209
13820
|
for (const name of [...jsonFiles, ...KEY_FILES]) {
|
|
13210
|
-
if (await exists(
|
|
13211
|
-
await copyFile(
|
|
13821
|
+
if (await exists(join20(legacyRoot, name))) {
|
|
13822
|
+
await copyFile(join20(legacyRoot, name), join20(backupDir, name));
|
|
13212
13823
|
}
|
|
13213
13824
|
}
|
|
13214
13825
|
const rekeyed = [];
|
|
13215
13826
|
for (const name of jsonFiles) {
|
|
13216
|
-
const raw = await
|
|
13827
|
+
const raw = await readFile18(join20(legacyRoot, name), "utf-8");
|
|
13217
13828
|
let parsed;
|
|
13218
13829
|
try {
|
|
13219
13830
|
parsed = JSON.parse(raw);
|
|
@@ -13223,13 +13834,13 @@ async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machine
|
|
|
13223
13834
|
const counter = { n: 0 };
|
|
13224
13835
|
const converted = rekeyValue(parsed, legacyKey, machineKey, counter);
|
|
13225
13836
|
if (counter.n === 0) continue;
|
|
13226
|
-
const tmp =
|
|
13227
|
-
await
|
|
13228
|
-
await
|
|
13837
|
+
const tmp = join20(legacyRoot, `${name}.tmp`);
|
|
13838
|
+
await writeFile17(tmp, JSON.stringify(converted, null, 2), "utf-8");
|
|
13839
|
+
await rename14(tmp, join20(legacyRoot, name));
|
|
13229
13840
|
rekeyed.push({ file: name, values: counter.n });
|
|
13230
13841
|
}
|
|
13231
13842
|
for (const { file, values } of rekeyed) {
|
|
13232
|
-
const parsed = JSON.parse(await
|
|
13843
|
+
const parsed = JSON.parse(await readFile18(join20(legacyRoot, file), "utf-8"));
|
|
13233
13844
|
const check = { n: 0 };
|
|
13234
13845
|
rekeyValue(parsed, machineKey, machineKey, check);
|
|
13235
13846
|
if (check.n !== values) {
|
|
@@ -13238,13 +13849,13 @@ async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machine
|
|
|
13238
13849
|
);
|
|
13239
13850
|
}
|
|
13240
13851
|
}
|
|
13241
|
-
await
|
|
13852
|
+
await mkdir20(machineRootDir, { recursive: true });
|
|
13242
13853
|
const moved = [];
|
|
13243
13854
|
const skipped = [];
|
|
13244
13855
|
for (const name of MACHINE_FILES) {
|
|
13245
|
-
const from =
|
|
13856
|
+
const from = join20(legacyRoot, name);
|
|
13246
13857
|
if (!await exists(from)) continue;
|
|
13247
|
-
const to =
|
|
13858
|
+
const to = join20(machineRootDir, name);
|
|
13248
13859
|
if (await exists(to)) {
|
|
13249
13860
|
skipped.push(name);
|
|
13250
13861
|
continue;
|
|
@@ -13254,7 +13865,7 @@ async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machine
|
|
|
13254
13865
|
moved.push(name);
|
|
13255
13866
|
}
|
|
13256
13867
|
for (const name of KEY_FILES) {
|
|
13257
|
-
const path =
|
|
13868
|
+
const path = join20(legacyRoot, name);
|
|
13258
13869
|
if (await exists(path)) await unlink5(path);
|
|
13259
13870
|
}
|
|
13260
13871
|
return { backupDir, rekeyed, moved, skipped };
|
|
@@ -13265,14 +13876,14 @@ import { createRequire } from "module";
|
|
|
13265
13876
|
import { randomBytes as randomBytes5, createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2 } from "crypto";
|
|
13266
13877
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
13267
13878
|
import {
|
|
13268
|
-
readFile as
|
|
13269
|
-
writeFile as
|
|
13270
|
-
rename as
|
|
13271
|
-
mkdir as
|
|
13879
|
+
readFile as readFile19,
|
|
13880
|
+
writeFile as writeFile18,
|
|
13881
|
+
rename as rename15,
|
|
13882
|
+
mkdir as mkdir21,
|
|
13272
13883
|
access as access2,
|
|
13273
|
-
readdir as
|
|
13884
|
+
readdir as readdir9
|
|
13274
13885
|
} from "fs/promises";
|
|
13275
|
-
import { join as
|
|
13886
|
+
import { join as join21, dirname as dirname10 } from "path";
|
|
13276
13887
|
var _require = createRequire(import.meta.url);
|
|
13277
13888
|
var PARSER_ID_MAP = {
|
|
13278
13889
|
"stack-monitor-parser-jsons": "native:json",
|
|
@@ -13381,7 +13992,7 @@ async function getSodium() {
|
|
|
13381
13992
|
return _sodium;
|
|
13382
13993
|
}
|
|
13383
13994
|
async function decryptLegacyFile(filePath, key, sodium) {
|
|
13384
|
-
const raw = (await
|
|
13995
|
+
const raw = (await readFile19(filePath, "utf-8")).trim();
|
|
13385
13996
|
if (!raw || raw === "[]" || raw === "{}") {
|
|
13386
13997
|
try {
|
|
13387
13998
|
return JSON.parse(raw);
|
|
@@ -13401,14 +14012,12 @@ async function decryptLegacyFile(filePath, key, sodium) {
|
|
|
13401
14012
|
}
|
|
13402
14013
|
async function atomicWrite(path, data) {
|
|
13403
14014
|
const tmp = path + ".tmp";
|
|
13404
|
-
await
|
|
13405
|
-
await
|
|
14015
|
+
await writeFile18(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
14016
|
+
await rename15(tmp, path);
|
|
13406
14017
|
}
|
|
13407
14018
|
async function runLegacyMigration(dataDir) {
|
|
13408
|
-
const
|
|
13409
|
-
const
|
|
13410
|
-
const legacyRoot = join18(abCwd, env.DATA_DIR);
|
|
13411
|
-
const legacyKeyPath = join18(legacyRoot, "dbs", "encryption-key.json");
|
|
14019
|
+
const legacyRoot = env.DATA_DIR;
|
|
14020
|
+
const legacyKeyPath = join21(legacyRoot, "dbs", "encryption-key.json");
|
|
13412
14021
|
try {
|
|
13413
14022
|
await access2(legacyKeyPath);
|
|
13414
14023
|
} catch {
|
|
@@ -13416,20 +14025,20 @@ async function runLegacyMigration(dataDir) {
|
|
|
13416
14025
|
}
|
|
13417
14026
|
console.log("[legacy-migration] Legacy Runeya data detected, starting migration\u2026");
|
|
13418
14027
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
|
|
13419
|
-
const legacyTmp =
|
|
13420
|
-
await
|
|
13421
|
-
await
|
|
13422
|
-
const legacyOld =
|
|
13423
|
-
await
|
|
14028
|
+
const legacyTmp = join21(abCwd, ".runeya-tmp");
|
|
14029
|
+
await rename15(legacyRoot, legacyTmp);
|
|
14030
|
+
await mkdir21(dataDir, { recursive: true });
|
|
14031
|
+
const legacyOld = join21(dataDir, `backup-${timestamp}`);
|
|
14032
|
+
await rename15(legacyTmp, legacyOld);
|
|
13424
14033
|
console.log(`[legacy-migration] Legacy data backed up to ${legacyOld}`);
|
|
13425
|
-
const keyJson = JSON.parse(await
|
|
14034
|
+
const keyJson = JSON.parse(await readFile19(join21(legacyOld, "dbs", "encryption-key.json"), "utf-8"));
|
|
13426
14035
|
const sodium = await getSodium();
|
|
13427
14036
|
const sodiumKey = sodium.from_base64(keyJson.encryptionKey);
|
|
13428
14037
|
const keyBuffer = Buffer.from(sodiumKey);
|
|
13429
14038
|
const runyeaKeyHex = keyBuffer.toString("hex");
|
|
13430
14039
|
await setEncryptionKey(runyeaKeyHex);
|
|
13431
14040
|
console.log("[legacy-migration] Encryption key loaded in memory (temporary)");
|
|
13432
|
-
const dbsDir =
|
|
14041
|
+
const dbsDir = join21(legacyOld, "dbs");
|
|
13433
14042
|
async function readEncrypted(filePath) {
|
|
13434
14043
|
try {
|
|
13435
14044
|
return await decryptLegacyFile(filePath, sodiumKey, sodium);
|
|
@@ -13438,29 +14047,29 @@ async function runLegacyMigration(dataDir) {
|
|
|
13438
14047
|
return null;
|
|
13439
14048
|
}
|
|
13440
14049
|
}
|
|
13441
|
-
const envsDir =
|
|
13442
|
-
const envFiles = (await
|
|
14050
|
+
const envsDir = join21(dbsDir, "envs");
|
|
14051
|
+
const envFiles = (await readdir9(envsDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
|
|
13443
14052
|
const legacyEnvs = {};
|
|
13444
14053
|
for (const file of envFiles) {
|
|
13445
14054
|
const name = file.replace(".encrypted.json", "");
|
|
13446
|
-
legacyEnvs[name] = await readEncrypted(
|
|
14055
|
+
legacyEnvs[name] = await readEncrypted(join21(envsDir, file));
|
|
13447
14056
|
}
|
|
13448
|
-
const servicesDir =
|
|
13449
|
-
const serviceFiles = (await
|
|
14057
|
+
const servicesDir = join21(dbsDir, "services");
|
|
14058
|
+
const serviceFiles = (await readdir9(servicesDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
|
|
13450
14059
|
const legacyServices = {};
|
|
13451
14060
|
for (const file of serviceFiles) {
|
|
13452
14061
|
const name = file.replace(".encrypted.json", "");
|
|
13453
14062
|
if (!name) continue;
|
|
13454
|
-
const data = await readEncrypted(
|
|
14063
|
+
const data = await readEncrypted(join21(servicesDir, file));
|
|
13455
14064
|
if (data?.label) legacyServices[name] = data;
|
|
13456
14065
|
}
|
|
13457
|
-
const overridesDir =
|
|
13458
|
-
const overrideFiles = (await
|
|
14066
|
+
const overridesDir = join21(dbsDir, "overrides");
|
|
14067
|
+
const overrideFiles = (await readdir9(overridesDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
|
|
13459
14068
|
const legacyServiceOverrides = {};
|
|
13460
14069
|
const legacyEnvOverrides = {};
|
|
13461
14070
|
for (const file of overrideFiles) {
|
|
13462
14071
|
const baseName = file.replace(".encrypted.json", "");
|
|
13463
|
-
const data = await readEncrypted(
|
|
14072
|
+
const data = await readEncrypted(join21(overridesDir, file));
|
|
13464
14073
|
if (!data) continue;
|
|
13465
14074
|
if (baseName.endsWith("-envs")) {
|
|
13466
14075
|
legacyServiceOverrides[baseName.slice(0, -5)] = data;
|
|
@@ -13468,11 +14077,11 @@ async function runLegacyMigration(dataDir) {
|
|
|
13468
14077
|
legacyEnvOverrides[baseName.slice(0, -12)] = data;
|
|
13469
14078
|
}
|
|
13470
14079
|
}
|
|
13471
|
-
const parsersDir =
|
|
13472
|
-
const parserFiles = (await
|
|
14080
|
+
const parsersDir = join21(dbsDir, "parsers");
|
|
14081
|
+
const parserFiles = (await readdir9(parsersDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
|
|
13473
14082
|
const legacyParsers = [];
|
|
13474
14083
|
for (const file of parserFiles) {
|
|
13475
|
-
const data = await readEncrypted(
|
|
14084
|
+
const data = await readEncrypted(join21(parsersDir, file));
|
|
13476
14085
|
if (data?.id) legacyParsers.push(data);
|
|
13477
14086
|
}
|
|
13478
14087
|
const projectMonoId = randomUUID12();
|
|
@@ -13813,7 +14422,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
13813
14422
|
}
|
|
13814
14423
|
let existingSettings = {};
|
|
13815
14424
|
try {
|
|
13816
|
-
existingSettings = JSON.parse(await
|
|
14425
|
+
existingSettings = JSON.parse(await readFile19(join21(dataDir, "settings.json"), "utf-8"));
|
|
13817
14426
|
} catch {
|
|
13818
14427
|
}
|
|
13819
14428
|
const migratedParsers = legacyParsers.map((p) => ({
|
|
@@ -13834,11 +14443,11 @@ ${p.transform ?? ""}`,
|
|
|
13834
14443
|
...migratedParsers
|
|
13835
14444
|
]
|
|
13836
14445
|
};
|
|
13837
|
-
await atomicWrite(
|
|
13838
|
-
await atomicWrite(
|
|
13839
|
-
await atomicWrite(
|
|
13840
|
-
await atomicWrite(
|
|
13841
|
-
await atomicWrite(
|
|
14446
|
+
await atomicWrite(join21(dataDir, "projects.json"), runeProjects);
|
|
14447
|
+
await atomicWrite(join21(dataDir, "services.json"), runeServices);
|
|
14448
|
+
await atomicWrite(join21(dataDir, "environments.json"), runeEnvs);
|
|
14449
|
+
await atomicWrite(join21(dataDir, "environment-overrides.json"), flatOverrides);
|
|
14450
|
+
await atomicWrite(join21(dataDir, "settings.json"), newSettings);
|
|
13842
14451
|
const svcEnvCount = runeEnvs.filter((e) => e.scope === "service").length;
|
|
13843
14452
|
console.log(`[legacy-migration] Written: ${runeProjects.length} projects, ${runeServices.length} services, ${runeEnvs.length} environments (${svcEnvCount} service-scoped)`);
|
|
13844
14453
|
console.log(`[legacy-migration] Overrides: ${Object.keys(flatOverrides).length}, custom parsers: ${migratedParsers.length} (all disabled \u2014 rewrite required)`);
|
|
@@ -13849,12 +14458,12 @@ ${p.transform ?? ""}`,
|
|
|
13849
14458
|
}
|
|
13850
14459
|
async function findLegacyKeyInBackup(dataDir) {
|
|
13851
14460
|
try {
|
|
13852
|
-
const entries = await
|
|
14461
|
+
const entries = await readdir9(dataDir);
|
|
13853
14462
|
const backupDirs = entries.filter((e) => e.startsWith("backup-")).sort((a, b) => a < b ? -1 : a > b ? 1 : 0).reverse();
|
|
13854
14463
|
for (const dir of backupDirs) {
|
|
13855
|
-
const keyJsonPath =
|
|
14464
|
+
const keyJsonPath = join21(dataDir, dir, "dbs", "encryption-key.json");
|
|
13856
14465
|
try {
|
|
13857
|
-
const raw = await
|
|
14466
|
+
const raw = await readFile19(keyJsonPath, "utf-8");
|
|
13858
14467
|
const parsed = JSON.parse(raw);
|
|
13859
14468
|
if (!parsed.encryptionKey) continue;
|
|
13860
14469
|
const sodium = await getSodium();
|
|
@@ -13872,18 +14481,17 @@ async function findLegacyKeyInBackup(dataDir) {
|
|
|
13872
14481
|
return null;
|
|
13873
14482
|
}
|
|
13874
14483
|
async function runOverrideOnlyMigration(dataDir) {
|
|
13875
|
-
const { resolve:
|
|
13876
|
-
const abDataDir =
|
|
13877
|
-
const abCwd = resolve6(process.cwd());
|
|
14484
|
+
const { resolve: resolve7 } = await import("path");
|
|
14485
|
+
const abDataDir = resolve7(dataDir);
|
|
13878
14486
|
const candidateDirs = [
|
|
13879
|
-
|
|
13880
|
-
|
|
14487
|
+
join21(dirname10(env.DATA_DIR), ".runeya", "dbs", "overrides"),
|
|
14488
|
+
join21(abDataDir, "dbs", "overrides")
|
|
13881
14489
|
];
|
|
13882
14490
|
let overridesDir = null;
|
|
13883
14491
|
let overrideFiles = [];
|
|
13884
14492
|
for (const dir of candidateDirs) {
|
|
13885
14493
|
try {
|
|
13886
|
-
const files = (await
|
|
14494
|
+
const files = (await readdir9(dir)).filter((f) => f.endsWith(".encrypted.json"));
|
|
13887
14495
|
if (files.length > 0) {
|
|
13888
14496
|
overridesDir = dir;
|
|
13889
14497
|
overrideFiles = files;
|
|
@@ -13893,7 +14501,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
13893
14501
|
}
|
|
13894
14502
|
}
|
|
13895
14503
|
if (!overridesDir || overrideFiles.length === 0) return false;
|
|
13896
|
-
const legacyKeyPath =
|
|
14504
|
+
const legacyKeyPath = join21(overridesDir, "..", "encryption-key.json");
|
|
13897
14505
|
try {
|
|
13898
14506
|
await access2(legacyKeyPath);
|
|
13899
14507
|
return false;
|
|
@@ -13911,7 +14519,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
13911
14519
|
const envNameToId = {};
|
|
13912
14520
|
let envsRaw;
|
|
13913
14521
|
try {
|
|
13914
|
-
envsRaw = JSON.parse(await
|
|
14522
|
+
envsRaw = JSON.parse(await readFile19(join21(abDataDir, "environments.json"), "utf-8"));
|
|
13915
14523
|
for (const _env of envsRaw) {
|
|
13916
14524
|
const env2 = _env;
|
|
13917
14525
|
if (env2["name"] && env2["id"]) {
|
|
@@ -13930,7 +14538,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
13930
14538
|
let servicesRaw = [];
|
|
13931
14539
|
const serviceNameToId = {};
|
|
13932
14540
|
try {
|
|
13933
|
-
servicesRaw = JSON.parse(await
|
|
14541
|
+
servicesRaw = JSON.parse(await readFile19(join21(abDataDir, "services.json"), "utf-8"));
|
|
13934
14542
|
for (const _svc of servicesRaw) {
|
|
13935
14543
|
const svc = _svc;
|
|
13936
14544
|
if (svc["name"] && svc["id"]) serviceNameToId[svc["name"]] = svc["id"];
|
|
@@ -13947,7 +14555,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
13947
14555
|
const envName = baseName.slice(0, -"-environment".length);
|
|
13948
14556
|
let data;
|
|
13949
14557
|
try {
|
|
13950
|
-
data = await decryptLegacyFile(
|
|
14558
|
+
data = await decryptLegacyFile(join21(overridesDir, file), sodiumKey, sodium);
|
|
13951
14559
|
} catch (err) {
|
|
13952
14560
|
console.warn(`[override-migration] Failed to decrypt ${file}: ${err.message}`);
|
|
13953
14561
|
continue;
|
|
@@ -13974,7 +14582,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
13974
14582
|
}
|
|
13975
14583
|
}
|
|
13976
14584
|
if (Object.keys(flatOverrides).length > 0) {
|
|
13977
|
-
await atomicWrite(
|
|
14585
|
+
await atomicWrite(join21(abDataDir, "environment-overrides.json"), flatOverrides);
|
|
13978
14586
|
console.log(`[override-migration] Written ${Object.keys(flatOverrides).length} override(s) to environment-overrides.json`);
|
|
13979
14587
|
}
|
|
13980
14588
|
let envsModified = false;
|
|
@@ -13984,7 +14592,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
13984
14592
|
const serviceName = baseName.slice(0, -"-envs".length);
|
|
13985
14593
|
let data;
|
|
13986
14594
|
try {
|
|
13987
|
-
data = await decryptLegacyFile(
|
|
14595
|
+
data = await decryptLegacyFile(join21(overridesDir, file), sodiumKey, sodium);
|
|
13988
14596
|
} catch (err) {
|
|
13989
14597
|
console.warn(`[override-migration] Failed to decrypt ${file}: ${err.message}`);
|
|
13990
14598
|
continue;
|
|
@@ -14043,12 +14651,12 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
14043
14651
|
}
|
|
14044
14652
|
}
|
|
14045
14653
|
if (envsModified) {
|
|
14046
|
-
await atomicWrite(
|
|
14654
|
+
await atomicWrite(join21(abDataDir, "environments.json"), envsRaw);
|
|
14047
14655
|
console.log("[override-migration] Merged service overrides into environments.json");
|
|
14048
14656
|
}
|
|
14049
14657
|
const migratedDir = overridesDir.replace(/overrides$/, "overrides-migrated");
|
|
14050
14658
|
try {
|
|
14051
|
-
await
|
|
14659
|
+
await rename15(overridesDir, migratedDir);
|
|
14052
14660
|
console.log(`[override-migration] Moved overrides/ \u2192 overrides-migrated/`);
|
|
14053
14661
|
} catch (err) {
|
|
14054
14662
|
console.warn(`[override-migration] Could not rename overrides dir: ${err.message}`);
|
|
@@ -14058,8 +14666,8 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
14058
14666
|
}
|
|
14059
14667
|
|
|
14060
14668
|
// ../server/src/services/server-state.ts
|
|
14061
|
-
import { join as
|
|
14062
|
-
import { stat as
|
|
14669
|
+
import { join as join22 } from "path";
|
|
14670
|
+
import { stat as stat10 } from "fs/promises";
|
|
14063
14671
|
var ENCRYPTED_KEY_FILENAME2 = ".encryption.key.enc";
|
|
14064
14672
|
var VERIFY_FILENAME2 = ".runeya-verify";
|
|
14065
14673
|
async function getServerState() {
|
|
@@ -14068,22 +14676,22 @@ async function getServerState() {
|
|
|
14068
14676
|
let hasVerify = false;
|
|
14069
14677
|
let hasData = false;
|
|
14070
14678
|
try {
|
|
14071
|
-
await
|
|
14679
|
+
await stat10(join22(dataDir, ENCRYPTED_KEY_FILENAME2));
|
|
14072
14680
|
hasEncFile = true;
|
|
14073
14681
|
} catch {
|
|
14074
14682
|
}
|
|
14075
14683
|
try {
|
|
14076
|
-
await
|
|
14684
|
+
await stat10(join22(dataDir, VERIFY_FILENAME2));
|
|
14077
14685
|
hasVerify = true;
|
|
14078
14686
|
} catch {
|
|
14079
14687
|
}
|
|
14080
14688
|
try {
|
|
14081
|
-
await
|
|
14689
|
+
await stat10(join22(dataDir, "projects.json"));
|
|
14082
14690
|
hasData = true;
|
|
14083
14691
|
} catch {
|
|
14084
14692
|
}
|
|
14085
14693
|
try {
|
|
14086
|
-
await
|
|
14694
|
+
await stat10(join22(dataDir, "project.json"));
|
|
14087
14695
|
hasData = true;
|
|
14088
14696
|
} catch {
|
|
14089
14697
|
}
|
|
@@ -14488,7 +15096,7 @@ async function createLocalServer() {
|
|
|
14488
15096
|
console.log("[server] Premier lancement \u2014 setup requis");
|
|
14489
15097
|
}
|
|
14490
15098
|
}
|
|
14491
|
-
const absoluteDataDir =
|
|
15099
|
+
const absoluteDataDir = resolve6(env.DATA_DIR);
|
|
14492
15100
|
console.log(`[server] Local project directory: ${absoluteDataDir}`);
|
|
14493
15101
|
console.log(`[server] Machine directory: ${machineRoot()}`);
|
|
14494
15102
|
if (existsSync2(env.DATA_DIR)) {
|
|
@@ -14536,9 +15144,10 @@ async function createLocalServer() {
|
|
|
14536
15144
|
parentIds: [],
|
|
14537
15145
|
color: "#22c55e",
|
|
14538
15146
|
immutable: true
|
|
14539
|
-
});
|
|
15147
|
+
}, project.orgId);
|
|
14540
15148
|
await projectStore.update({
|
|
14541
15149
|
id: project.id,
|
|
15150
|
+
orgId: project.orgId,
|
|
14542
15151
|
environmentIds: [localEnv.id],
|
|
14543
15152
|
activeEnvironmentId: localEnv.id
|
|
14544
15153
|
});
|
|
@@ -15126,12 +15735,12 @@ async function createLocalServer() {
|
|
|
15126
15735
|
]);
|
|
15127
15736
|
server.closeAllConnections();
|
|
15128
15737
|
await Promise.race([
|
|
15129
|
-
new Promise((
|
|
15130
|
-
new Promise((
|
|
15738
|
+
new Promise((resolve7) => wss.close(() => resolve7())),
|
|
15739
|
+
new Promise((resolve7) => setTimeout(resolve7, 2e3))
|
|
15131
15740
|
]);
|
|
15132
15741
|
await Promise.race([
|
|
15133
|
-
new Promise((
|
|
15134
|
-
new Promise((
|
|
15742
|
+
new Promise((resolve7) => server.close(() => resolve7())),
|
|
15743
|
+
new Promise((resolve7) => setTimeout(resolve7, 2e3))
|
|
15135
15744
|
]);
|
|
15136
15745
|
console.log("[server] Shutdown complete");
|
|
15137
15746
|
};
|
|
@@ -15152,7 +15761,7 @@ var PullEnvError = class extends Error {
|
|
|
15152
15761
|
};
|
|
15153
15762
|
async function promptPassword() {
|
|
15154
15763
|
process.stderr.write("Master password: ");
|
|
15155
|
-
return new Promise((
|
|
15764
|
+
return new Promise((resolve7) => {
|
|
15156
15765
|
const stdin = process.stdin;
|
|
15157
15766
|
const wasRaw = stdin.isRaw;
|
|
15158
15767
|
if (stdin.isTTY) stdin.setRawMode(true);
|
|
@@ -15165,7 +15774,7 @@ async function promptPassword() {
|
|
|
15165
15774
|
stdin.pause();
|
|
15166
15775
|
stdin.removeListener("data", onData);
|
|
15167
15776
|
process.stderr.write("\n");
|
|
15168
|
-
|
|
15777
|
+
resolve7(input);
|
|
15169
15778
|
} else if (ch === "") {
|
|
15170
15779
|
process.exit(1);
|
|
15171
15780
|
} else if (ch === "\x7F" || ch === "\b") {
|
|
@@ -15228,7 +15837,7 @@ if (process.env["RUNEYA_DATA_DIR"]) {
|
|
|
15228
15837
|
console.log(`[server] Working directory: ${resolved} (from RUNEYA_DATA_DIR)`);
|
|
15229
15838
|
}
|
|
15230
15839
|
function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500) {
|
|
15231
|
-
return new Promise((
|
|
15840
|
+
return new Promise((resolve7, reject) => {
|
|
15232
15841
|
let attempts = 0;
|
|
15233
15842
|
const wssErrorHandler = () => {
|
|
15234
15843
|
};
|
|
@@ -15249,7 +15858,7 @@ function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500)
|
|
|
15249
15858
|
server.listen(port, host, () => {
|
|
15250
15859
|
server.removeListener("error", onError);
|
|
15251
15860
|
wss?.removeListener("error", wssErrorHandler);
|
|
15252
|
-
|
|
15861
|
+
resolve7();
|
|
15253
15862
|
});
|
|
15254
15863
|
};
|
|
15255
15864
|
tryListen();
|
|
@@ -15296,4 +15905,4 @@ export {
|
|
|
15296
15905
|
createLocalServer,
|
|
15297
15906
|
pullEnv
|
|
15298
15907
|
};
|
|
15299
|
-
//# sourceMappingURL=src-
|
|
15908
|
+
//# sourceMappingURL=src-5QPLS5TQ.js.map
|