@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.
Files changed (297) hide show
  1. package/agent/index.js +5 -5
  2. package/agent/index.js.map +1 -1
  3. package/agent-manager-JJYZDLNB.js +7 -0
  4. package/{chunk-B6C5AXMK.js → chunk-5PRQBES2.js} +247 -63
  5. package/chunk-5PRQBES2.js.map +1 -0
  6. package/index.js +1 -1
  7. package/package.json +1 -1
  8. package/{src-ZKQETCHR.js → src-5QPLS5TQ.js} +1102 -493
  9. package/src-5QPLS5TQ.js.map +1 -0
  10. package/web/assets/{AgentScalarChatInterface.vue-IDhv2vYG.js → AgentScalarChatInterface.vue-Z3BLbola.js} +1 -1
  11. package/web/assets/AgentTerminalPage-BlQ3k7VX.css +1 -0
  12. package/web/assets/AgentTerminalPage-Dg4kOMbK.js +1 -0
  13. package/web/assets/AiChatPage-0igbt0Ie.js +1 -0
  14. package/web/assets/AppLayout-BQpUnN2N.js +1 -0
  15. package/web/assets/{AppLayout-Bqs7ug1K.css → AppLayout-D1bdzemo.css} +1 -1
  16. package/web/assets/{AuditPage-D__LOvkN.js → AuditPage-zfim5OLa.js} +1 -1
  17. package/web/assets/{CloudAccountPage-ll1uAGyN.js → CloudAccountPage-BD-L27Wl.js} +1 -1
  18. package/web/assets/CloudAccountPage-CHZG-vv-.js +1 -0
  19. package/web/assets/CloudMessagingPage-BnaGxzNK.js +2 -0
  20. package/web/assets/CloudMessagingPage-x4bDmgyb.css +1 -0
  21. package/web/assets/{DashboardPage-XFl45skz.js → DashboardPage-ByzcpJV7.js} +1 -1
  22. package/web/assets/{EditorIcon-DHxnxi15.js → EditorIcon-jk0Nz8Mr.js} +1 -1
  23. package/web/assets/{EnvironmentForm-D_JXws5q.js → EnvironmentForm-BH4EMxM6.js} +1 -1
  24. package/web/assets/{EnvironmentFormPage-7YhkQZnF.js → EnvironmentFormPage-Bwjr8SsV.js} +1 -1
  25. package/web/assets/EnvironmentListPage-BgqHe-4p.js +1 -0
  26. package/web/assets/EnvironmentListPage-BwgJscFQ.css +1 -0
  27. package/web/assets/{EnvironmentSelector-zX2RSq7p.js → EnvironmentSelector-CBh21uuY.js} +1 -1
  28. package/web/assets/{KanbanBoardPage-CYiVnEsW.js → KanbanBoardPage-Dg0Od24D.js} +3 -3
  29. package/web/assets/{KanbanListPage-DA8VLR_A.js → KanbanListPage-C6bsD-gJ.js} +1 -1
  30. package/web/assets/LaunchProjectPage-BZe2kaRt.js +1 -0
  31. package/web/assets/LaunchProjectPage-Bqm88kwd.css +1 -0
  32. package/web/assets/LaunchServicesPage-B4xd4APa.js +1 -0
  33. package/web/assets/LaunchServicesPage-pWREsZTD.css +1 -0
  34. package/web/assets/{LogViewer-DfbwNIUv.js → LogViewer-BhaSYgvw.js} +1 -1
  35. package/web/assets/{ProcessControls-CZV4iizX.js → ProcessControls-lDF1qNIM.js} +1 -1
  36. package/web/assets/{ProcessDetailPage-CeatPGNs.js → ProcessDetailPage-DwaApuHs.js} +1 -1
  37. package/web/assets/{ProcessMetrics-C5U4PvOh.js → ProcessMetrics-rGmADRtS.js} +1 -1
  38. package/web/assets/ProjectDetailPage-BoIcnBMH.js +1 -0
  39. package/web/assets/ProjectDetailPage-hcU-rKhJ.css +1 -0
  40. package/web/assets/ProjectListPage-C3JtBL-6.js +1 -0
  41. package/web/assets/ProjectListPage-DYs6X7r2.css +1 -0
  42. package/web/assets/{RandomJobTestPage-ycTTIaQd.js → RandomJobTestPage-CndKGKYR.js} +1 -1
  43. package/web/assets/{ScenarioEditorPage-C1EV-XSW.js → ScenarioEditorPage-DU7tb4M-.js} +1 -1
  44. package/web/assets/{SendToChatDialog-28Mlo-hb.js → SendToChatDialog-CCRCitBB.js} +1 -1
  45. package/web/assets/{ServiceControlsBar-CdWFcbXZ.js → ServiceControlsBar-BjyMYoiN.js} +1 -1
  46. package/web/assets/{ServiceDetailPage-BvoD3SB6.css → ServiceDetailPage-BRg1byDZ.css} +1 -1
  47. package/web/assets/ServiceDetailPage-JqpmujmY.js +2 -0
  48. package/web/assets/ServiceFormPage-B8jcMT3b.css +1 -0
  49. package/web/assets/ServiceFormPage-DgNwiYzM.js +1 -0
  50. package/web/assets/{SettingsPage-B1jaxJGj.js → SettingsPage-BZM3WWqc.js} +4 -4
  51. package/web/assets/{UnlockPage-ktTtBOr2.js → UnlockPage-BcZ2vL57.js} +1 -1
  52. package/web/assets/{VariablesTable-B9Nhdk1_.js → VariablesTable-0Gz2ffiL.js} +1 -1
  53. package/web/assets/{VaultMigrationPage-Dy48s3x-.js → VaultMigrationPage-BxTBDL19.js} +1 -1
  54. package/web/assets/{VersionBadge-BdlVr8Tg.js → VersionBadge-DvsaGqvh.js} +1 -1
  55. package/web/assets/WikiPage-DkEQXkzp.js +1 -0
  56. package/web/assets/{WikiView-CJDAiZr5.js → WikiView-e81ejcA8.js} +1 -1
  57. package/web/assets/{add-scalar-classes-pA8osZfz.js → add-scalar-classes-Bh8ghfnQ.js} +3 -3
  58. package/web/assets/auth-errors-C1B7h06f.js +1 -0
  59. package/web/assets/{autocomplete-N91is6wh.js → autocomplete-CDWkaHAi.js} +1 -1
  60. package/web/assets/{basic-shape-diamond.svg-BllfU_oB.js → basic-shape-diamond.svg-7zqPzGW0.js} +1 -1
  61. package/web/assets/{basic-shape-hexagon.svg-DW5bh6IP.js → basic-shape-hexagon.svg-DQwxqMd2.js} +1 -1
  62. package/web/assets/{basic-shape-primary-circle-ellipse-round.svg-9uuUiv5o.js → basic-shape-primary-circle-ellipse-round.svg-Bqm796vK.js} +1 -1
  63. package/web/assets/{basic-shape-primary-square-rectangle.svg-B64Eo9nB.js → basic-shape-primary-square-rectangle.svg-D7eyO04i.js} +1 -1
  64. package/web/assets/{basic-shape-shield.svg-DOPLFilS.js → basic-shape-shield.svg-Bnc_pFVA.js} +1 -1
  65. package/web/assets/{browser-SZtn0i0X.js → browser-DeXg6zUY.js} +1 -1
  66. package/web/assets/{browser-DueDd93t.js → browser-LxxhMX-Z.js} +1 -1
  67. package/web/assets/{card-COABLB3K.js → card-RaXQSIfv.js} +1 -1
  68. package/web/assets/{checkbox-B_GT2gG5.js → checkbox-DnpUXs7B.js} +1 -1
  69. package/web/assets/{chip--NXSwDoR.js → chip-0f0r_-zR.js} +1 -1
  70. package/web/assets/{client-BhFbvoQd.js → client-CTzGkOjO.js} +1 -1
  71. package/web/assets/cloud-chat.store-D2oJ_DFn.js +1 -0
  72. package/web/assets/{cloud-session-BSsRB6P3.js → cloud-session-BLn7b3ke.js} +1 -1
  73. package/web/assets/{column-BtS8Diqq.js → column-DqAehgUU.js} +1 -1
  74. package/web/assets/{computer-device-desktop-monitor.svg-BzjC7P3F.js → computer-device-desktop-monitor.svg-k2x9Oiiu.js} +1 -1
  75. package/web/assets/{computer-device-desktop.svg-BecVq71S.js → computer-device-desktop.svg-CyFljvrh.js} +1 -1
  76. package/web/assets/{computer-device-laptop.svg-Dme_bl6m.js → computer-device-laptop.svg-ZKpV0gtW.js} +1 -1
  77. 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
  78. package/web/assets/{computer-device-mobile-phone-android-samsung.svg--sSW4sfn.js → computer-device-mobile-phone-android-samsung.svg-gpUXFo9R.js} +1 -1
  79. 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
  80. package/web/assets/{computer-device-mobile-phone-iphone-x.svg-C2Uy1WjT.js → computer-device-mobile-phone-iphone-x.svg-BsPF_njN.js} +1 -1
  81. package/web/assets/{computer-device-mobile-tablet-touch.svg-CaeEG4WK.js → computer-device-mobile-tablet-touch.svg-DON7qRSP.js} +1 -1
  82. package/web/assets/{computer-device-mobile-tablet.svg-BpXku7YF.js → computer-device-mobile-tablet.svg-BPnT3MvS.js} +1 -1
  83. package/web/assets/{computer-device-network-ethernet-cat6.svg-aNAbOQCS.js → computer-device-network-ethernet-cat6.svg-Bpjv_2ub.js} +1 -1
  84. package/web/assets/{computer-device-network-lan-www.svg-ClT8Xavc.js → computer-device-network-lan-www.svg-BhXBIyrf.js} +1 -1
  85. package/web/assets/{computer-device-network-wifi-connection.svg-BBVSSCLm.js → computer-device-network-wifi-connection.svg-DUaA1GcU.js} +1 -1
  86. package/web/assets/{computer-device-network-wifi-router.svg-DHEuRciK.js → computer-device-network-wifi-router.svg-k_DqQho1.js} +1 -1
  87. package/web/assets/{cssMode-X03N01aE.js → cssMode-CfVTS8OM.js} +1 -1
  88. package/web/assets/{divider-B8p6MBJX.js → divider-C1huit3M.js} +1 -1
  89. package/web/assets/{ecology-science-erlenmeyer-flask.svg-2iSCfC0H.js → ecology-science-erlenmeyer-flask.svg-D7pbgLTT.js} +1 -1
  90. package/web/assets/{fieldset-B9fGvXQ_.js → fieldset-BVRjgmg4.js} +1 -1
  91. package/web/assets/{freemarker2-BR_8OWIK.js → freemarker2-DsY_gjrP.js} +1 -1
  92. package/web/assets/{handlebars-B3tZa--p.js → handlebars-mh1HrGq7.js} +1 -1
  93. package/web/assets/{html-BZBK1HMw.js → html-BsaAhzOa.js} +1 -1
  94. package/web/assets/{htmlMode-BVt6cDn5.js → htmlMode-pIhI3cAi.js} +1 -1
  95. package/web/assets/{image-flash-lightning.svg-CGclepmP.js → image-flash-lightning.svg-DDbhd-eu.js} +1 -1
  96. package/web/assets/{image-picture-flower.svg-Cl3xtcRn.js → image-picture-flower.svg-Bbx1pu02.js} +1 -1
  97. package/web/assets/{index-BqCmf99K.css → index-D7XqcLhE.css} +1 -1
  98. package/web/assets/{index-BPW4FgHw.js → index-Do0zA8cN.js} +215 -215
  99. package/web/assets/{interface-alert-exclamation-diamond.svg-CmooSrfz.js → interface-alert-exclamation-diamond.svg-DUJeN1hs.js} +1 -1
  100. package/web/assets/{interface-alert-exclamation-triangle-warning.svg-CkbjudIG.js → interface-alert-exclamation-triangle-warning.svg-5y99yZSY.js} +1 -1
  101. package/web/assets/{interface-alert-information-circle.svg-BVOJniJB.js → interface-alert-information-circle.svg-DPGP01ZU.js} +1 -1
  102. package/web/assets/{interface-award-crown.svg-BtCDhizx.js → interface-award-crown.svg-GDQ8ZRUh.js} +1 -1
  103. package/web/assets/{interface-bookmark.svg-CI3C_CEm.js → interface-bookmark-tag.svg-D1NUMLB5.js} +1 -1
  104. package/web/assets/{interface-bookmark-tag.svg-DpZpJCxG.js → interface-bookmark.svg-CTZlXD4R.js} +1 -1
  105. package/web/assets/{interface-calendar-date-one.svg-DeSRwc9I.js → interface-calendar-date-one.svg-DXo4raY8.js} +1 -1
  106. package/web/assets/{interface-content-book-open-pages.svg-CsYHQiGg.js → interface-content-book-open-pages.svg-DTsCVw_D.js} +1 -1
  107. package/web/assets/{interface-content-book-page.svg-DusdhNh8.js → interface-content-book-page.svg-w9azHLUe.js} +1 -1
  108. package/web/assets/{interface-content-file.svg-BZpRl2vg.js → interface-content-file.svg-69CCnbNb.js} +1 -1
  109. package/web/assets/{interface-content-folder.svg-BHyjskUa.js → interface-content-folder.svg-85PnJNaK.js} +1 -1
  110. package/web/assets/interface-content-folder.svg-Db3hJMdF.js +1 -0
  111. package/web/assets/{interface-copy-clipboard.svg-CU8KQ56k.js → interface-copy-clipboard.svg-B0AfVljN.js} +1 -1
  112. package/web/assets/{interface-edit-attachment.svg-sLGtjIVg.js → interface-edit-attachment.svg-mFLLqqcw.js} +1 -1
  113. package/web/assets/{interface-edit-binocular.svg-Kr0btLKm.js → interface-edit-binocular.svg-CHzgny3z.js} +1 -1
  114. package/web/assets/{interface-edit-magic-wand.svg-DuZj2AYt.js → interface-edit-magic-wand.svg-BbJg9LMT.js} +1 -1
  115. package/web/assets/{interface-edit-tool-paint-roller.svg-Da6-5RAS.js → interface-edit-tool-paint-roller.svg-YKE28Xpc.js} +1 -1
  116. package/web/assets/{interface-edit-tool-pencil.svg-BoPw8ms_.js → interface-edit-tool-pencil.svg-Cy12MfEi.js} +1 -1
  117. package/web/assets/{interface-favorite-award.svg-C3ITWbq3.js → interface-favorite-award.svg-COiLJu4w.js} +1 -1
  118. package/web/assets/{interface-favorite-flag.svg-GCr6RXH0.js → interface-favorite-flag.svg-C0NtaPD_.js} +1 -1
  119. package/web/assets/{interface-favorite-heart.svg-BY9dqVB0.js → interface-favorite-heart.svg-CcKJOjOh.js} +1 -1
  120. package/web/assets/{interface-favorite-star.svg-uA4scusZ.js → interface-favorite-star.svg-C3vRaRtM.js} +1 -1
  121. package/web/assets/{interface-favorite-stars-sparkles.svg-BdSCoHYl.js → interface-favorite-stars-sparkles.svg-DN9WOkFs.js} +1 -1
  122. package/web/assets/{interface-hierarchy-flowchart.svg-CxNFchvq.js → interface-hierarchy-flowchart.svg-CffgUWNS.js} +1 -1
  123. package/web/assets/{interface-home-house.svg-DkrKouBb.js → interface-home-house.svg-B9c4N9Fi.js} +1 -1
  124. package/web/assets/{interface-hyperlink.svg-I-GxkQJY.js → interface-hyperlink.svg-B8dXB276.js} +1 -1
  125. package/web/assets/{interface-lighting-brightness.svg-BJR3bOQ-.js → interface-lighting-brightness.svg-BjFVAOW3.js} +1 -1
  126. package/web/assets/{interface-lock-closed.svg-C0b_bVUd.js → interface-lock-closed.svg-DwNcKmwu.js} +1 -1
  127. package/web/assets/{interface-lock-open-unlock.svg-BtmiNLaj.js → interface-lock-open-unlock.svg-BXZeKlHt.js} +1 -1
  128. package/web/assets/{interface-login-key.svg-DmYxn7RL.js → interface-login-key.svg-CQnZGVMJ.js} +1 -1
  129. package/web/assets/{interface-search.svg-C1pVTe7C.js → interface-search.svg-CXre9Ja4.js} +1 -1
  130. package/web/assets/{interface-setting-cog.svg-C42e2OTW.js → interface-setting-cog.svg-CGK2QxqV.js} +1 -1
  131. package/web/assets/{interface-share-megaphone-bullhorn.svg-o9tOlEh2.js → interface-share-megaphone-bullhorn.svg-BcdTvuin.js} +1 -1
  132. package/web/assets/{interface-share-rocket.svg-Da9sYj_t.js → interface-share-rocket.svg-DIvKBIQX.js} +1 -1
  133. package/web/assets/{interface-share-satellite.svg-B20KpyfR.js → interface-share-satellite.svg-DNbqAorz.js} +1 -1
  134. package/web/assets/{interface-share-space-ship.svg-B16T8uJ6.js → interface-share-space-ship.svg-Biobo33q.js} +1 -1
  135. package/web/assets/{interface-share.svg-Bwk_5ajK.js → interface-share.svg-DWjt5rBb.js} +1 -1
  136. package/web/assets/{interface-signal-square.svg-CAVSymjP.js → interface-signal-square.svg--hmv5CmE.js} +1 -1
  137. package/web/assets/{interface-time-clock-circle.svg-fjss8ru1.js → interface-time-clock-circle.svg-C1GnMIQD.js} +1 -1
  138. package/web/assets/{interface-time-hour-glass.svg-DaTX8FMo.js → interface-time-hour-glass.svg-BCRsHIbm.js} +1 -1
  139. package/web/assets/{interface-users-multiple.svg-D-EFFOw7.js → interface-users-multiple.svg-CUpeUldF.js} +1 -1
  140. package/web/assets/{interface-weather-moon.svg-CrrnEbU7.js → interface-weather-moon.svg-QXMY6B27.js} +1 -1
  141. package/web/assets/{javascript-C6MNjbYD.js → javascript-DKeyayKI.js} +1 -1
  142. package/web/assets/{jsonMode-CA2Tf-2T.js → jsonMode-I9zemAn2.js} +1 -1
  143. package/web/assets/{liquid-DBKs6KRv.js → liquid-xEdzGxP1.js} +1 -1
  144. package/web/assets/{lspLanguageFeatures-BPpii0dk.js → lspLanguageFeatures-DOVjCuiu.js} +1 -1
  145. package/web/assets/{mail-chat-bubble-square.svg-lR62-873.js → mail-chat-bubble-square.svg-CR8byO78.js} +1 -1
  146. package/web/assets/{mail-send-email-paper-airplane.svg-Bkg6W9qr.js → mail-send-email-paper-airplane.svg-B0cIMUKH.js} +1 -1
  147. package/web/assets/{mail-send-envelope.svg-CbNm7aaE.js → mail-send-envelope.svg-El4e7bXy.js} +1 -1
  148. package/web/assets/{mdx-nUeiRsUz.js → mdx-AWoH37r-.js} +1 -1
  149. package/web/assets/{message-D6qRK6Jo.js → message-CATQqbP_.js} +1 -1
  150. package/web/assets/{minus-DoeouXPt.js → minus-Sd1wO9DO.js} +1 -1
  151. package/web/assets/modal-BbDElcmR.js +1 -0
  152. package/web/assets/{modal-kvCAAvat.js → modal-ZXFHQ6vP.js} +1 -1
  153. package/web/assets/{money-cashier-receipt.svg-DV9XlLpx.js → money-cashier-receipt.svg-BIEGQvlX.js} +1 -1
  154. package/web/assets/{money-currency-dollar-pay.svg-_jLRI9-z.js → money-currency-dollar-pay.svg-BgCHAYq5.js} +1 -1
  155. package/web/assets/{money-graph-arrow-increase.svg-Tnvl7X21.js → money-graph-arrow-increase.svg-q6aDu3og.js} +1 -1
  156. package/web/assets/{money-graph-bar-chart-increase.svg-uo4bmQha.js → money-graph-bar-chart-increase.svg-mRvG_dWg.js} +1 -1
  157. package/web/assets/{multiselect-BJUl25Yx.js → multiselect-BK7hJt7W.js} +1 -1
  158. package/web/assets/{nature-ecology-leaf.svg-CFuPp8bO.js → nature-ecology-leaf.svg-DdhvC8mq.js} +1 -1
  159. package/web/assets/{password-Cmgp-gwd.js → password-Dnf9oU5a.js} +1 -1
  160. package/web/assets/{phone-telephone.svg-CPKz1YHt.js → phone-telephone.svg-CjbyxKZA.js} +1 -1
  161. package/web/assets/{plus-s_OJLoyY.js → plus-jYeRYaw9.js} +1 -1
  162. package/web/assets/process.store-C5LXrPXo.js +1 -0
  163. package/web/assets/{programming-bug.svg-fxsVnaeH.js → programming-bug.svg-DvDCWw4P.js} +1 -1
  164. package/web/assets/{programming-cloud.svg-BnU51aLl.js → programming-cloud.svg-2prWAqB7.js} +1 -1
  165. package/web/assets/{programming-computer-database-server.svg-BZgDGKJW.js → programming-computer-database-server.svg-CT7k5d_p.js} +1 -1
  166. package/web/assets/{programming-computer-database.svg-B5S5rAvT.js → programming-computer-database.svg-Dm7fpvII.js} +1 -1
  167. package/web/assets/{programming-module-four-layout.svg-QRzEO0n6.js → programming-module-four-layout.svg-DBv6QvEO.js} +1 -1
  168. package/web/assets/{programming-module-three.svg-CM_P7Vlm.js → programming-module-three.svg-BnP96bPJ.js} +1 -1
  169. package/web/assets/{programming-module.svg-3GTFIIEX.js → programming-module.svg-Bdr4ZdZO.js} +1 -1
  170. package/web/assets/{programming-script-code.svg-KtOz6fyB.js → programming-script-code.svg-DIcr6sF-.js} +1 -1
  171. package/web/assets/{python-Dm07kugX.js → python-C554d-pk.js} +1 -1
  172. package/web/assets/{razor-bhq-c8Pc.js → razor-BvOiTjBp.js} +1 -1
  173. package/web/assets/router-CCEIu6U5.js +1 -0
  174. package/web/assets/{settings.store-DkR2x1Gd.js → settings.store-DISlY-48.js} +1 -1
  175. package/web/assets/{shopping-cart.svg-z5f-zUgD.js → shopping-cart.svg-DoCVSOin.js} +1 -1
  176. package/web/assets/{shopping-gift-present.svg-BuM8QoB9.js → shopping-gift-present.svg-CgNqOSQr.js} +1 -1
  177. package/web/assets/{shopping-shipping-box-parcel-package.svg-CWw8lTQj.js → shopping-shipping-box-parcel-package.svg-YkVPEp7J.js} +1 -1
  178. package/web/assets/{src-BNgzvl34.js → src-Cvt5pswj.js} +1 -1
  179. package/web/assets/{src-G9u1k3Z0.js → src-DKVQlKvZ.js} +1 -1
  180. package/web/assets/{tabpanel-C_MAwvD8.js → tabpanel-DFoLVdFR.js} +1 -1
  181. package/web/assets/{tag-new-circle.svg-C2PwVxge.js → tag-new-circle.svg-BeUe12X2.js} +1 -1
  182. package/web/assets/{travel-map-earth-globe.svg-Cva7Lt5l.js → travel-map-earth-globe.svg-B1I1Ed-v.js} +1 -1
  183. package/web/assets/trpc-CVPkysoa.js +1 -0
  184. package/web/assets/{tsMode-CiozJEFc.js → tsMode-BPD-0Vep.js} +1 -1
  185. package/web/assets/{typescript-DjFjYcp9.js → typescript-Q2uKnndS.js} +1 -1
  186. package/web/assets/useAiChatDrawer-B70XYuU8.js +1 -0
  187. package/web/assets/useCloudOrg-CZ68epfW.js +1 -0
  188. package/web/assets/useProjectCatalog-Kh_sHbSD.js +1 -0
  189. package/web/assets/{xml-_A4d3R4e.js → xml-B0CfAsFk.js} +1 -1
  190. package/web/assets/{yaml-B2BOTmvS.js → yaml-B3zRI6AS.js} +1 -1
  191. package/web/index.html +2 -2
  192. package/agent-manager-RQEJLSSF.js +0 -7
  193. package/chunk-B6C5AXMK.js.map +0 -1
  194. package/src-ZKQETCHR.js.map +0 -1
  195. package/web/assets/AgentTerminalPage-BNUuSKXa.js +0 -1
  196. package/web/assets/AgentTerminalPage-CQxk6jVs.css +0 -1
  197. package/web/assets/AiChatPage-i_HxoGlU.js +0 -1
  198. package/web/assets/AppLayout-BtVQtKnv.js +0 -1
  199. package/web/assets/CloudAccountPage-CEf5yWA2.js +0 -1
  200. package/web/assets/CloudMessagingPage-B63PMN88.js +0 -2
  201. package/web/assets/CloudMessagingPage-DbhpPX4q.css +0 -1
  202. package/web/assets/EnvironmentListPage-BDqcug66.css +0 -1
  203. package/web/assets/EnvironmentListPage-DyNlU_ys.js +0 -1
  204. package/web/assets/LaunchProjectPage-B7ch8hBp.js +0 -1
  205. package/web/assets/LaunchProjectPage-CxXC2U3O.css +0 -1
  206. package/web/assets/LaunchServicesPage-BAvD32G1.css +0 -1
  207. package/web/assets/LaunchServicesPage-BxbnthGj.js +0 -1
  208. package/web/assets/ProjectDetailPage-B42imx75.css +0 -1
  209. package/web/assets/ProjectDetailPage-BaZIvewr.js +0 -1
  210. package/web/assets/ProjectListPage-BGU6SKAr.css +0 -1
  211. package/web/assets/ProjectListPage-BreaFS_h.js +0 -1
  212. package/web/assets/ServiceDetailPage-DAq3ofhT.js +0 -2
  213. package/web/assets/ServiceFormPage-BER91b-6.js +0 -1
  214. package/web/assets/ServiceFormPage-D31sr_k0.css +0 -1
  215. package/web/assets/WikiPage-CMqO84ak.js +0 -1
  216. package/web/assets/auth-errors-B3tAiztt.js +0 -1
  217. package/web/assets/cloud-chat.store-DEKUnKhm.js +0 -1
  218. package/web/assets/interface-content-folder.svg-CE1AjWC2.js +0 -1
  219. package/web/assets/modal-OTFsRnDn.js +0 -1
  220. package/web/assets/process.store-dKQhHReg.js +0 -1
  221. package/web/assets/router-BamtimoJ.js +0 -1
  222. package/web/assets/trpc-BdB_KPws.js +0 -1
  223. package/web/assets/useAiChatDrawer-DpUd_4Sa.js +0 -1
  224. package/web/assets/useCloudOrg-DcDNmhwp.js +0 -1
  225. /package/{agent-manager-RQEJLSSF.js.map → agent-manager-JJYZDLNB.js.map} +0 -0
  226. /package/web/assets/{abap-lJFTLuI6.js → abap-DMmvWZUy.js} +0 -0
  227. /package/web/assets/{apex-Bp0KWav8.js → apex-D89PiTzU.js} +0 -0
  228. /package/web/assets/{azcli-DBOe7Uvy.js → azcli-ChnjTTA5.js} +0 -0
  229. /package/web/assets/{bat-Dk_NT_by.js → bat-uLzQ_FhK.js} +0 -0
  230. /package/web/assets/{bicep-B07Mrlc7.js → bicep-CMOK7Q1v.js} +0 -0
  231. /package/web/assets/{cameligo-DL5eNXGn.js → cameligo-D1AyojFH.js} +0 -0
  232. /package/web/assets/{clojure-BzV2Yqu_.js → clojure-D9JoUgXn.js} +0 -0
  233. /package/web/assets/{coffee-Bjlj5hmf.js → coffee-D-5sSToL.js} +0 -0
  234. /package/web/assets/{cpp-C9oth5qz.js → cpp-BqT_t_y9.js} +0 -0
  235. /package/web/assets/{csharp-HyGd049S.js → csharp-CuToYI91.js} +0 -0
  236. /package/web/assets/{csp-DjCDOBuX.js → csp-CffcxvlO.js} +0 -0
  237. /package/web/assets/{css-DIL8RTJ3.js → css-CUD0biQW.js} +0 -0
  238. /package/web/assets/{cypher-DX1zPXL5.js → cypher-ByyWL_9U.js} +0 -0
  239. /package/web/assets/{dart-BBQZd_ZZ.js → dart-B__ipcUn.js} +0 -0
  240. /package/web/assets/{dockerfile-Cj0CaWYx.js → dockerfile-DS-_8bWw.js} +0 -0
  241. /package/web/assets/{ecl-BjmWCTJg.js → ecl-DWsNCBUj.js} +0 -0
  242. /package/web/assets/{elixir-3uWoEjXX.js → elixir-OfjEy-3g.js} +0 -0
  243. /package/web/assets/{flow9-D4r739e9.js → flow9-BPX22hxS.js} +0 -0
  244. /package/web/assets/{fsharp-B3ycAx5T.js → fsharp-CVJcfIUn.js} +0 -0
  245. /package/web/assets/{go-DFHBjfCl.js → go-CfRHZ3yg.js} +0 -0
  246. /package/web/assets/{graphql-DBjIbM_e.js → graphql-BZaO3ttK.js} +0 -0
  247. /package/web/assets/{hcl-Buu_1Hsp.js → hcl-BF9-qUku.js} +0 -0
  248. /package/web/assets/{ini-CgzZhgz_.js → ini-BUvw6xhr.js} +0 -0
  249. /package/web/assets/{java-BczDO0q5.js → java-BZ5BNd2x.js} +0 -0
  250. /package/web/assets/{julia-B142kn-7.js → julia-DN38cgYT.js} +0 -0
  251. /package/web/assets/{kotlin-42XMP7p1.js → kotlin-CjEJKT8q.js} +0 -0
  252. /package/web/assets/{less-BCIsKRqU.js → less-DMEH_Nwq.js} +0 -0
  253. /package/web/assets/{lexon-DTDOH1dQ.js → lexon-CKQUX8Ov.js} +0 -0
  254. /package/web/assets/{lua-BrH-CeNs.js → lua-CuWjKLJ0.js} +0 -0
  255. /package/web/assets/{m3-D-eUeHOI.js → m3-CEp1bVrM.js} +0 -0
  256. /package/web/assets/{markdown-DwpSjZTn.js → markdown-DxtQ_puq.js} +0 -0
  257. /package/web/assets/{mips-DoGTrjVk.js → mips-Dx7qydpO.js} +0 -0
  258. /package/web/assets/{msdax-CQ8McnjQ.js → msdax-DpNCaSiO.js} +0 -0
  259. /package/web/assets/{mysql-Cj3do0ZO.js → mysql-C3xsBDPP.js} +0 -0
  260. /package/web/assets/{objective-c-BZbR8TG8.js → objective-c-BbaVHCX1.js} +0 -0
  261. /package/web/assets/{pascal-23PRDHRq.js → pascal-sDju2faq.js} +0 -0
  262. /package/web/assets/{pascaligo-CxasKTLJ.js → pascaligo-ChCdOH6b.js} +0 -0
  263. /package/web/assets/{perl-Dhxz2Lhx.js → perl-BCgLFj01.js} +0 -0
  264. /package/web/assets/{pgsql-BIbUy7yD.js → pgsql-DtKby69f.js} +0 -0
  265. /package/web/assets/{php-B5qS9qgM.js → php-NYvR17Qb.js} +0 -0
  266. /package/web/assets/{pla-Bolz9fha.js → pla-gSg-RBG0.js} +0 -0
  267. /package/web/assets/{postiats-Ba6dE5lj.js → postiats-BQxHyDfy.js} +0 -0
  268. /package/web/assets/{powerquery-2jW3ONgo.js → powerquery-DzDmbVZN.js} +0 -0
  269. /package/web/assets/{powershell-CMfBIiJp.js → powershell-yy4Q5BEQ.js} +0 -0
  270. /package/web/assets/{protobuf-BpaquOUj.js → protobuf-B_hIk9q0.js} +0 -0
  271. /package/web/assets/{pug-cBG82Ydz.js → pug-CdeEb1ft.js} +0 -0
  272. /package/web/assets/{qsharp-DtqL8Lmt.js → qsharp-CiSquEgT.js} +0 -0
  273. /package/web/assets/{r-CKLew4sj.js → r-D1Ocz1x4.js} +0 -0
  274. /package/web/assets/{redis-BdDxRU-N.js → redis-D42Y12yz.js} +0 -0
  275. /package/web/assets/{redshift-X4a4MlqW.js → redshift-CS62MYfG.js} +0 -0
  276. /package/web/assets/{restructuredtext-LA4Fv05h.js → restructuredtext-CKFSMEXw.js} +0 -0
  277. /package/web/assets/{ruby-CibE_WVM.js → ruby-C4gTaeU3.js} +0 -0
  278. /package/web/assets/{rust-BK6RsiL0.js → rust-C_f2AiHQ.js} +0 -0
  279. /package/web/assets/{sb-BnCCt0Qj.js → sb-CdNdpP1r.js} +0 -0
  280. /package/web/assets/{scala-oaBDrVqj.js → scala-N4EWaFTL.js} +0 -0
  281. /package/web/assets/{scheme-C-wkLC01.js → scheme-CttIARxx.js} +0 -0
  282. /package/web/assets/{scss-BceVGOJY.js → scss-v0wtbxaE.js} +0 -0
  283. /package/web/assets/{shell-CxhBAFA5.js → shell-BJdEWXbc.js} +0 -0
  284. /package/web/assets/{solidity-D9sXgTfx.js → solidity-DKDa59zZ.js} +0 -0
  285. /package/web/assets/{sophia-C4VBhIEM.js → sophia--Kw-vDpr.js} +0 -0
  286. /package/web/assets/{sparql-DbsK2s9y.js → sparql-BI8jS9Pc.js} +0 -0
  287. /package/web/assets/{sql-CuGfE0wH.js → sql-Dz6z--6O.js} +0 -0
  288. /package/web/assets/{st-rB6Bqb2h.js → st-CT8D05gv.js} +0 -0
  289. /package/web/assets/{swift-BtHGe_4T.js → swift-Ayb3KzWx.js} +0 -0
  290. /package/web/assets/{systemverilog-CgKnzwU4.js → systemverilog-BtWJM9O5.js} +0 -0
  291. /package/web/assets/{tcl-DyqQWf87.js → tcl-BJvUWpbV.js} +0 -0
  292. /package/web/assets/{twig-C980XO5P.js → twig-c3YaBCU6.js} +0 -0
  293. /package/web/assets/{typespec-CVmA80px.js → typespec-gFCWLBoZ.js} +0 -0
  294. /package/web/assets/{uuid-2Kb5PDpq.js → uuid-B3B8HIK9.js} +0 -0
  295. /package/web/assets/{vb-rdNQv5VQ.js → vb-CVtfWXV2.js} +0 -0
  296. /package/web/assets/{w3c-keyname-B2B-WOb1.js → w3c-keyname-nn0G3QC2.js} +0 -0
  297. /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
- rootOfProject,
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-B6C5AXMK.js";
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 resolve5 } from "path";
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.id, {
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.id, root);
573
- setProjectServices(p.id, p.serviceIds ?? []);
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.id, root);
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(id) {
682
+ async get(target) {
648
683
  await this.load();
649
- return this.projects.get(id) ?? null;
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.id, project);
670
- setProjectVault(project.id, vaultFor(project).root);
671
- setProjectServices(project.id, project.serviceIds);
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 existing = this.projects.get(input.id);
678
- if (!existing) return null;
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(input.id, updated);
690
- setProjectVault(updated.id, vaultFor(updated).root);
691
- setProjectServices(updated.id, updated.serviceIds);
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(id) {
735
+ async delete(target) {
696
736
  await this.load();
697
- const project = this.projects.get(id);
698
- if (!project) return false;
699
- this.projects.delete(id);
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.id);
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(projectId) {
767
+ async forgetLocally(target) {
727
768
  await this.load();
728
- const project = this.projects.get(projectId);
729
- if (!project) return;
730
- this.projects.delete(projectId);
731
- forgetProject(projectId);
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(projectId);
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 existing = this.projects.get(id);
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(id, updated);
769
- setProjectVault(id, vaultFor(updated).root);
770
- setProjectServices(id, updated.serviceIds);
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(id, project);
776
- setProjectVault(id, vaultFor(project).root);
777
- setProjectServices(id, project.serviceIds);
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(id) {
825
+ async applyRemoteDelete(target) {
783
826
  await this.load();
784
- const project = this.projects.get(id);
785
- if (!project) return false;
786
- this.projects.delete(id);
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(projectId, serviceId) {
835
+ async addService(target, serviceId) {
792
836
  await this.load();
793
- const project = this.projects.get(projectId);
794
- if (!project) return null;
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(projectId, updated);
798
- setProjectServices(projectId, updated.serviceIds);
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(projectId, serviceId) {
847
+ async removeService(target, serviceId) {
803
848
  await this.load();
804
- const project = this.projects.get(projectId);
805
- if (!project) return null;
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(projectId, updated);
808
- setProjectServices(projectId, updated.serviceIds);
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(projectId, environmentId) {
858
+ async addEnvironment(target, environmentId) {
813
859
  await this.load();
814
- const project = this.projects.get(projectId);
815
- if (!project) return null;
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(projectId, updated);
868
+ this.projects.set(key, updated);
822
869
  await this.save();
823
870
  return updated;
824
871
  }
825
- async removeEnvironment(projectId, environmentId) {
872
+ async removeEnvironment(target, environmentId) {
826
873
  await this.load();
827
- const project = this.projects.get(projectId);
828
- if (!project) return null;
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(projectId, updated);
883
+ this.projects.set(key, updated);
836
884
  await this.save();
837
885
  return updated;
838
886
  }
839
- async setActiveEnvironment(projectId, environmentId) {
887
+ async setActiveEnvironment(target, environmentId) {
840
888
  await this.load();
841
- const project = this.projects.get(projectId);
842
- if (!project) return null;
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(projectId, updated);
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 projectId = await environmentStore.projectIdOfVariable(variableId);
880
- if (projectId) return rootOfProject(projectId);
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
- get: protectedProcedure.input(z2.object({ id: z2.string().max(128) })).query(async ({ input }) => {
1468
- const project = await projectStore.get(input.id);
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.id);
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.id);
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({ projectId: z2.string().max(128), serviceId: z2.string().max(128) })).mutation(async ({ input }) => {
1551
- const project = await projectStore.addService(input.projectId, input.serviceId);
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(input.projectId, svcEnv.id);
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({ projectId: z2.string().max(128), serviceId: z2.string().max(128) })).mutation(async ({ input }) => {
1581
- const project = await projectStore.removeService(input.projectId, input.serviceId);
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({ projectId: z2.string().max(128), environmentId: z2.string().max(128) })).mutation(async ({ input }) => {
1595
- const project = await projectStore.get(input.projectId);
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(input.projectId, input.environmentId);
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 readFile5, writeFile as writeFile5, rename as rename5, mkdir as mkdir5, chmod as chmod4 } from "fs/promises";
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 mkdir5(machineRoot(), { recursive: true });
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 writeFile5(tmpPath, JSON.stringify(data, null, 2), "utf-8");
1684
- await rename5(tmpPath, filePath);
1685
- await chmod4(filePath, 384);
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 readFile5(path, "utf-8"));
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
- cwd: service.cwd || ".",
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
- cwd: service.cwd || "."
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 resolve6 = null;
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 (resolve6) {
2152
- resolve6();
2153
- resolve6 = null;
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
- resolve6 = r;
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 resolve6 = null;
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 (resolve6) {
2185
- resolve6();
2186
- resolve6 = null;
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
- resolve6 = r;
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 resolve6 = null;
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 (resolve6) {
2216
- resolve6();
2217
- resolve6 = null;
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
- resolve6 = r;
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
- { id: input.processId, command: input.command, cwd: input.cwd, commandId: input.commandId },
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({ projectId: z8.string().max(128) })).query(async ({ input }) => {
2586
- return await environmentStore.list(input.projectId);
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({ projectId: z8.string().max(128), scope: z8.enum(["global", "service"]) })).query(async ({ input }) => {
2594
- return await environmentStore.listByScope(input.projectId, input.scope);
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).mutation(async ({ input }) => {
2600
- const env2 = await environmentStore.create(input);
2601
- const project = await projectStore.get(input.projectId);
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(input.projectId, env2.id);
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(input.projectId, svcEnv.id);
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
- cwd: process.cwd(),
2829
- dataDir: resolve(env.DATA_DIR),
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((resolve6) => setTimeout(resolve6, 500));
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 stat2 } from "fs/promises";
3380
- import { resolve as resolve2, join as join5 } from "path";
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 readFile6, writeFile as writeFile6, rename as rename6, mkdir as mkdir6, chmod as chmod5 } from "fs/promises";
3385
- import { join as join4 } from "path";
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 = join4(homedir(), ".runeya");
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 mkdir6(CONFIG_DIR, { recursive: true });
3695
+ await mkdir7(CONFIG_DIR, { recursive: true });
3406
3696
  }
3407
3697
  async atomicWrite(data) {
3408
3698
  await this.ensureDir();
3409
- const filePath = join4(CONFIG_DIR, CONFIG_FILE);
3699
+ const filePath = join6(CONFIG_DIR, CONFIG_FILE);
3410
3700
  const tmpPath = filePath + ".tmp";
3411
- await writeFile6(tmpPath, JSON.stringify(data, null, 2), "utf-8");
3412
- await rename6(tmpPath, filePath);
3413
- await chmod5(filePath, 384);
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 readFile6(join4(CONFIG_DIR, CONFIG_FILE), "utf-8");
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(projectId) {
3721
+ async getEditorPreferences(ref) {
3432
3722
  const config = await this.getConfig();
3433
- return config[projectId]?.settings?.editors ?? {};
3723
+ return config[configKey(ref)]?.settings?.editors ?? config[ref.id]?.settings?.editors ?? {};
3434
3724
  }
3435
- async setEditorPreference(projectId, target, editorKey) {
3725
+ async setEditorPreference(ref, target, editorKey) {
3436
3726
  const config = await this.getConfig();
3437
- if (!config[projectId]) config[projectId] = {};
3438
- if (!config[projectId].settings) config[projectId].settings = {};
3439
- if (!config[projectId].settings.editors) config[projectId].settings.editors = {};
3440
- config[projectId].settings.editors[target] = editorKey;
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(projectId, target) {
3734
+ async removeEditorPreference(ref, target) {
3444
3735
  const config = await this.getConfig();
3445
- const editors = config[projectId]?.settings?.editors;
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
- const rawCwd = service.cwd?.trim() || ".";
3469
- return resolveCwd(rawCwd);
3759
+ return resolveCwd(await resolveWorkspaceCwd({ serviceId }));
3470
3760
  }
3471
3761
  async function isCommandAvailable(command) {
3472
- return new Promise((resolve6) => {
3762
+ return new Promise((resolve7) => {
3473
3763
  execFile2("which", [command], { timeout: 5e3 }, (err) => {
3474
- resolve6(!err);
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({ projectId: z11.string().max(128) })).query(async ({ input }) => {
3489
- return userConfigManager.getEditorPreferences(input.projectId);
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(input.projectId, "default", input.editorKey);
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(input.projectId, input.serviceId, input.editorKey);
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(input.projectId, input.serviceId);
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 stat2(resolvedPath);
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 = join5(cwd, input.relativePath);
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 stat2(filePath);
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 mkdir7, writeFile as writeFile7 } from "fs/promises";
3578
- import { join as join6, dirname as dirname3 } from "path";
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 = join6(process.cwd(), input.relativePath);
3626
- await mkdir7(dirname3(abs), { recursive: true });
3627
- await writeFile7(abs, input.content, { encoding: "utf-8", mode: 420 });
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 readFile8 } from "fs/promises";
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 join8 } from "path";
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 readFile7, writeFile as writeFile8, rename as rename7, mkdir as mkdir8, chmod as chmod6 } from "fs/promises";
3961
- import { dirname as dirname4, join as join7 } from "path";
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 FILENAME3 = "conversations.private.json";
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(FILENAME3);
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(FILENAME3);
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 readFile7(await this.getFilePath(), "utf-8");
4304
+ return await readFile8(await this.getFilePath(), "utf-8");
3998
4305
  } catch {
3999
- return readFile7(this.getLegacyFilePath(), "utf-8");
4306
+ return readFile8(this.getLegacyFilePath(), "utf-8");
4000
4307
  }
4001
4308
  }
4002
4309
  async ensureDir() {
4003
- await mkdir8(dirname4(await this.getFilePath()), { recursive: true });
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(FILENAME3)];
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: join7(root, FILENAME3), legacy: false }))
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 readFile7(path, "utf-8"));
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, dirname4(path));
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 mkdir8(root, { recursive: true });
4112
- const filePath = join7(root, FILENAME3);
4419
+ await mkdir9(root, { recursive: true });
4420
+ const filePath = join9(root, FILENAME4);
4113
4421
  const tmpPath = `${filePath}.tmp`;
4114
- await writeFile8(tmpPath, JSON.stringify(data, null, 2), "utf-8");
4115
- await rename7(tmpPath, filePath);
4116
- await chmod6(filePath, 384);
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
- if (conv.projectId) return rootOfProject(conv.projectId);
4127
- return this.roots.get(conv.id) ?? dirname4(await keyedFilePath(FILENAME3));
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 = join8(homedir3(), ".claude", "projects");
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 = join8(PROJECTS_DIR, dir, filename);
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 readFile8(path, "utf-8");
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(join8(PROJECTS_DIR, dir), (_event, filename) => {
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 join9 } from "path";
4425
- var SESSIONS_DIR = join9(homedir4(), ".codex", "sessions");
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 = join9(dir, entry.name);
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((resolve6, reject) => {
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
- resolve6(parse(clean));
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((resolve6, reject) => {
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
- resolve6(parseCodexStatusText(buffer));
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 mkdir10, rm as rm2, readFile as readFile10 } from "fs/promises";
5397
- import { join as join10 } from "path";
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 chmod7, mkdir as mkdir9, rename as rename8, writeFile as writeFile9, readFile as readFile9 } from "fs/promises";
5401
- import { dirname as dirname5, resolve as resolve3, sep } from "path";
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 mkdir9(dirname5(filePath), { recursive: true });
5740
+ await mkdir10(dirname6(filePath), { recursive: true });
5416
5741
  const tmpPath = `${filePath}.tmp`;
5417
- await writeFile9(tmpPath, content, "utf-8");
5418
- await rename8(tmpPath, filePath);
5419
- await chmod7(filePath, 384);
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 readFile9(filePath, "utf-8");
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 mkdir10(dir, { recursive: true });
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(join10(dir, "README.md"), overview);
5475
- await atomicWriteSecure(join10(dir, "USER_REQUEST.md"), `# User request
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(join10(dir, fileName), content);
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 mkdir10(dir, { recursive: true });
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(join10(dir, "run-state.json"), data);
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(join10(dir, "run-state.json"));
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 readFile10(join10(dir, "USER_REQUEST.md"), "utf-8");
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 readFile10(join10(dir, "README.md"), "utf-8");
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 mkdir10(dir, { recursive: true });
5921
+ await mkdir11(dir, { recursive: true });
5597
5922
  await atomicWriteJsonSecure(
5598
- join10(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE),
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(join10(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE));
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 mkdir11, writeFile as writeFile10, readFile as readFile11, unlink as unlink2, readdir as readdir4 } from "fs/promises";
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 join11, extname, resolve as resolve4, sep as sep2 } from "path";
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 mkdir11(this.dir, { recursive: true, mode: 448 });
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 = join11(this.dir, fileName);
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 writeFile10(filePath, buffer, { mode: 384 });
6021
+ await writeFile11(filePath, buffer, { mode: 384 });
5697
6022
  const meta = { imageId, filePath, mimeType, originalName, createdAt: Date.now(), contextId };
5698
- await writeFile10(`${filePath}.meta.json`, JSON.stringify(meta), { encoding: "utf-8", mode: 384 });
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 readFile11(join11(this.dir, metaFile), "utf-8"));
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 readFile11(join11(this.dir, file), "utf-8"));
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 readFile11(result.filePath);
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 readFile11(join11(this.dir, file), "utf-8"));
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 readFile12, writeFile as writeFile11, rename as rename9, mkdir as mkdir12, chmod as chmod8, unlink as unlink3 } from "fs/promises";
5845
- import { dirname as dirname6 } from "path";
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 FILENAME4 = "cloud-session.json";
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(FILENAME4);
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(FILENAME4);
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 readFile12(await this.getFilePath(), "utf-8");
6198
+ return await readFile13(await this.getFilePath(), "utf-8");
5874
6199
  } catch {
5875
- return readFile12(this.getLegacyFilePath(), "utf-8");
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 mkdir12(dirname6(await this.getFilePath()), { recursive: true });
6247
+ await mkdir13(dirname7(await this.getFilePath()), { recursive: true });
5923
6248
  const filePath = await this.getFilePath();
5924
6249
  const tmpPath = filePath + ".tmp";
5925
- await writeFile11(tmpPath, JSON.stringify(this.session, null, 2), "utf-8");
5926
- await rename9(tmpPath, filePath);
5927
- await chmod8(filePath, 384);
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
- return { linked: await cloudSessionStore.isLinked() };
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((resolve6) => {
6910
+ await new Promise((resolve7) => {
6573
6911
  const tick = setInterval(() => {
6574
6912
  if (stopped) {
6575
6913
  clearInterval(tick);
6576
6914
  clearInterval(poll);
6577
- resolve6();
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-RQEJLSSF.js");
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((resolve6, reject) => {
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
- resolve6(result);
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((resolve6, reject) => {
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
- resolve6({ ...ctx.earlyResult, actualSessionId: streamState.actualSessionId });
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
- resolve6({ output: streamState.fullContent, tokenStatus: status, tokenMessage: message, branchChoice, actualSessionId: streamState.actualSessionId });
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((resolve6, reject) => {
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: resolve6, reject, timer });
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((resolve6) => {
7155
- proc.on("exit", () => resolve6());
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-RQEJLSSF.js");
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((resolve6, reject) => {
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(() => resolve6({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
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(() => resolve6({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
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((resolve6, reject) => {
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(() => resolve6({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
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(() => resolve6({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
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 resolve6 = null;
8452
+ let resolve7 = null;
8107
8453
  const handler = (event) => {
8108
8454
  if (queue.length >= 500) queue.shift();
8109
8455
  queue.push(event);
8110
- if (resolve6) {
8111
- resolve6();
8112
- resolve6 = null;
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
- resolve6 = r;
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: input.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 resolve6 = null;
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 (resolve6) {
8834
- resolve6();
8835
- resolve6 = null;
9208
+ if (resolve7) {
9209
+ resolve7();
9210
+ resolve7 = null;
8836
9211
  }
8837
9212
  },
8838
9213
  () => {
8839
9214
  stopped = true;
8840
- if (resolve6) {
8841
- resolve6();
8842
- resolve6 = null;
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
- resolve6 = r;
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
- cwd: process.cwd(),
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: service.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
- return { agentId, cwd: "." };
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 resolve6 = null;
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 (resolve6) {
9240
- resolve6();
9241
- resolve6 = null;
9621
+ if (resolve7) {
9622
+ resolve7();
9623
+ resolve7 = null;
9242
9624
  }
9243
9625
  },
9244
9626
  () => {
9245
9627
  done = true;
9246
- if (resolve6) {
9247
- resolve6();
9248
- resolve6 = null;
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
- resolve6 = r;
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 mkdir13 } from "fs/promises";
9778
+ import { mkdir as mkdir14 } from "fs/promises";
9397
9779
  import { randomUUID as randomUUID7 } from "crypto";
9398
- import { join as join12 } from "path";
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((resolve6) => {
9994
+ await new Promise((resolve7) => {
9613
9995
  const onAbort = () => {
9614
9996
  bus.listeners.delete(notify);
9615
- resolve6();
9997
+ resolve7();
9616
9998
  };
9617
9999
  const notify = () => {
9618
10000
  signal.removeEventListener("abort", onAbort);
9619
- resolve6();
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
- resolve6();
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((resolve6, reject) => {
10516
+ const answer = await new Promise((resolve7, reject) => {
10131
10517
  const key = pendingNodeKey(conversationId, node.id);
10132
- pendingNodeInputs.set(key, resolve6);
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 = join12(dir, `${slug}_${safeId}.md`);
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 mkdir13(dir, { recursive: true });
10571
+ await mkdir14(dir, { recursive: true });
10186
10572
  const { appendFile: appendFile2 } = await import("fs/promises");
10187
- await appendFile2(join12(dir, "README.md"), line, "utf-8");
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 = join12(dir, "FINAL.md");
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((resolve6, reject) => {
10923
+ const answer = await new Promise((resolve7, reject) => {
10538
10924
  const key = pendingNodeKey(conversationId, node.id);
10539
- pendingNodeInputs.set(key, resolve6);
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((resolve6) => {
11464
+ await new Promise((resolve7) => {
11073
11465
  const onAbort = () => {
11074
11466
  job.listeners.delete(notify);
11075
- resolve6();
11467
+ resolve7();
11076
11468
  };
11077
11469
  const notify = () => {
11078
11470
  signal.removeEventListener("abort", onAbort);
11079
- resolve6();
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
- resolve6();
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 resolve6 = null;
11750
+ let resolve7 = null;
11359
11751
  const handler = () => {
11360
11752
  if (queue.length >= 50) queue.shift();
11361
11753
  queue.push(kanbanQueue.getState());
11362
- if (resolve6) {
11363
- resolve6();
11364
- resolve6 = null;
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
- resolve6 = r;
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 mkdir14, readdir as readdir5, rm as rm3, stat as stat3, writeFile as writeFile12 } from "fs/promises";
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 join13 } from "path";
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 join13(tmpdir(), "runeya-kanban-attachments");
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 = join13(dir, name);
11448
- const info = await stat3(path).catch(() => null);
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(), join13(machineRoot(), "kanban-attachments")];
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 mkdir14(dir, { recursive: true });
11462
- const path = join13(dir, safeLocalName(id, name));
11463
- await writeFile12(path, body);
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
- { cwd: input.cwd, cols: input.cols, rows: input.rows }
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 resolve6 = null;
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 (resolve6) {
11716
- resolve6();
11717
- resolve6 = null;
12122
+ if (resolve7) {
12123
+ resolve7();
12124
+ resolve7 = null;
11718
12125
  }
11719
12126
  },
11720
12127
  () => {
11721
12128
  done = true;
11722
- if (resolve6) {
11723
- resolve6();
11724
- resolve6 = null;
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
- resolve6 = r;
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((resolve6) => setTimeout(resolve6, input.ms));
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 readFile13, readdir as readdir6, stat as stat4, realpath, writeFile as writeFile13, mkdir as mkdir15, rename as rename10, rm as rm4 } from "fs/promises";
11766
- import { resolve as resolvePath, join as join14, relative, sep as sep3, dirname as dirname7 } from "path";
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 mkdir15(dataDirAbs, { recursive: true });
11804
- const base = dirname7(await realpath(dataDirAbs));
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 mkdir15(target, { recursive: true });
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() || service.cwd || ".").toString();
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 = join14(dir, e.name);
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 = dirname7(target);
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 stat4(file);
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 readFile13(file, "utf-8");
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 mkdir15(dirname7(file), { recursive: true });
11917
- await writeFile13(file, input.content, "utf-8");
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 stat4(file);
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 mkdir15(dirname7(file), { recursive: true });
11936
- await writeFile13(file, "", "utf-8");
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 mkdir15(dir, { recursive: true });
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 stat4(to);
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 mkdir15(dirname7(to), { recursive: true });
11962
- await rename10(from, to);
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() || service.cwd || ".").toString();
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 readFile14, writeFile as writeFile14, rename as rename11, mkdir as mkdir16, chmod as chmod9 } from "fs/promises";
12004
- var FILENAME5 = "cloud-sync.json";
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(FILENAME5);
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(FILENAME5);
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 readFile14(this.getFilePath(), "utf-8");
12431
+ return await readFile15(this.getFilePath(), "utf-8");
12025
12432
  } catch {
12026
- return readFile14(this.getLegacyFilePath(), "utf-8");
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 mkdir16(machineRoot(), { recursive: true });
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 writeFile14(tmpPath, data, "utf-8");
12047
- await rename11(tmpPath, filePath);
12048
- await chmod9(filePath, 384);
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 mkdir18, rm as rm5 } from "fs/promises";
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 readFile15, unlink as unlink4, mkdir as mkdir17 } from "fs/promises";
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 join15 } from "path";
12489
+ import { join as join17 } from "path";
12081
12490
  var held = /* @__PURE__ */ new Set();
12082
12491
  function lockPath(orgId) {
12083
- return join15(orgVaultRoot(orgId), "org.lock");
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 readFile15(path, "utf-8"));
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 mkdir17(orgVaultRoot(orgId), { recursive: true });
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
- return false;
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 mkdir18(projectVaultRoot(this.link.orgId, projectId), { recursive: true });
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") await serviceStore.applyRemoteDelete(id);
12596
- else if (name === "environments") await environmentStore.applyRemoteDelete(id);
12597
- else await projectStore.applyRemoteDelete(id);
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 readFile16, writeFile as writeFile15, chmod as chmod10, stat as stat5, rename as rename12 } from "fs/promises";
12784
- import { join as join16 } from "path";
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 stat5(filePath);
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 readFile16(filePath);
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(join16(dataDir, PEPPER_FILENAME));
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 = join16(dataDir, PEPPER_FILENAME);
13275
+ const filePath = join18(dataDir, PEPPER_FILENAME);
12826
13276
  const tmpPath = filePath + ".tmp";
12827
- await writeFile15(tmpPath, pepper.toString("hex"), "utf-8");
12828
- await chmod10(tmpPath, 384);
12829
- await rename12(tmpPath, filePath);
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 = join16(dataDir, ENCRYPTED_KEY_FILENAME);
13326
+ const filePath = join18(dataDir, ENCRYPTED_KEY_FILENAME);
12877
13327
  const tmpPath = filePath + ".tmp";
12878
- await writeFile15(tmpPath, encoded, "utf-8");
12879
- await chmod10(tmpPath, 384);
12880
- await rename12(tmpPath, filePath);
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 = join16(dataDir, ENCRYPTED_KEY_FILENAME);
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 = join16(dataDir, VERIFY_FILENAME);
13353
+ const filePath = join18(dataDir, VERIFY_FILENAME);
12904
13354
  const tmpPath = filePath + ".tmp";
12905
- await writeFile15(tmpPath, payload.toString("base64"), "utf-8");
12906
- await rename12(tmpPath, filePath);
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 = join16(dataDir, VERIFY_FILENAME);
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(join16(dataDir, VERIFY_FILENAME))).toString("utf-8").trim();
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 stat5(join16(dataDir, VERIFY_FILENAME));
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 stat5(join16(dataDir, ENCRYPTED_KEY_FILENAME));
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
- const pulled = await cloudSyncBridge.followProject(input.orgId, input.projectId);
13023
- return { pulled, state: syncState(input.orgId) };
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 chmod11, stat as stat6 } from "fs/promises";
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 stat6(filepath);
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 chmod11(filepath, SECURE_MODE);
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 readdir7, readFile as readFile17, writeFile as writeFile16, mkdir as mkdir19, copyFile, unlink as unlink5, rename as rename13, stat as stat7 } from "fs/promises";
13155
- import { join as join17 } from "path";
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 readdir7(root, { withFileTypes: true });
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 stat7(path);
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 = join17(legacyRoot, `backup-${stamp}`);
13207
- await mkdir19(backupDir, { recursive: true });
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(join17(legacyRoot, name))) {
13211
- await copyFile(join17(legacyRoot, name), join17(backupDir, name));
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 readFile17(join17(legacyRoot, name), "utf-8");
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 = join17(legacyRoot, `${name}.tmp`);
13227
- await writeFile16(tmp, JSON.stringify(converted, null, 2), "utf-8");
13228
- await rename13(tmp, join17(legacyRoot, name));
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 readFile17(join17(legacyRoot, file), "utf-8"));
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 mkdir19(machineRootDir, { recursive: true });
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 = join17(legacyRoot, name);
13856
+ const from = join20(legacyRoot, name);
13246
13857
  if (!await exists(from)) continue;
13247
- const to = join17(machineRootDir, name);
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 = join17(legacyRoot, name);
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 readFile18,
13269
- writeFile as writeFile17,
13270
- rename as rename14,
13271
- mkdir as mkdir20,
13879
+ readFile as readFile19,
13880
+ writeFile as writeFile18,
13881
+ rename as rename15,
13882
+ mkdir as mkdir21,
13272
13883
  access as access2,
13273
- readdir as readdir8
13884
+ readdir as readdir9
13274
13885
  } from "fs/promises";
13275
- import { join as join18 } from "path";
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 readFile18(filePath, "utf-8")).trim();
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 writeFile17(tmp, JSON.stringify(data, null, 2), "utf-8");
13405
- await rename14(tmp, path);
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 { resolve: resolve6 } = await import("path");
13409
- const abCwd = resolve6(process.cwd());
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 = join18(abCwd, ".runeya-tmp");
13420
- await rename14(legacyRoot, legacyTmp);
13421
- await mkdir20(dataDir, { recursive: true });
13422
- const legacyOld = join18(dataDir, `backup-${timestamp}`);
13423
- await rename14(legacyTmp, legacyOld);
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 readFile18(join18(legacyOld, "dbs", "encryption-key.json"), "utf-8"));
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 = join18(legacyOld, "dbs");
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 = join18(dbsDir, "envs");
13442
- const envFiles = (await readdir8(envsDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
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(join18(envsDir, file));
14055
+ legacyEnvs[name] = await readEncrypted(join21(envsDir, file));
13447
14056
  }
13448
- const servicesDir = join18(dbsDir, "services");
13449
- const serviceFiles = (await readdir8(servicesDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
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(join18(servicesDir, file));
14063
+ const data = await readEncrypted(join21(servicesDir, file));
13455
14064
  if (data?.label) legacyServices[name] = data;
13456
14065
  }
13457
- const overridesDir = join18(dbsDir, "overrides");
13458
- const overrideFiles = (await readdir8(overridesDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
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(join18(overridesDir, file));
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 = join18(dbsDir, "parsers");
13472
- const parserFiles = (await readdir8(parsersDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
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(join18(parsersDir, file));
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 readFile18(join18(dataDir, "settings.json"), "utf-8"));
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(join18(dataDir, "projects.json"), runeProjects);
13838
- await atomicWrite(join18(dataDir, "services.json"), runeServices);
13839
- await atomicWrite(join18(dataDir, "environments.json"), runeEnvs);
13840
- await atomicWrite(join18(dataDir, "environment-overrides.json"), flatOverrides);
13841
- await atomicWrite(join18(dataDir, "settings.json"), newSettings);
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 readdir8(dataDir);
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 = join18(dataDir, dir, "dbs", "encryption-key.json");
14464
+ const keyJsonPath = join21(dataDir, dir, "dbs", "encryption-key.json");
13856
14465
  try {
13857
- const raw = await readFile18(keyJsonPath, "utf-8");
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: resolve6 } = await import("path");
13876
- const abDataDir = resolve6(dataDir);
13877
- const abCwd = resolve6(process.cwd());
14484
+ const { resolve: resolve7 } = await import("path");
14485
+ const abDataDir = resolve7(dataDir);
13878
14486
  const candidateDirs = [
13879
- join18(abCwd, ".runeya", "dbs", "overrides"),
13880
- join18(abDataDir, "dbs", "overrides")
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 readdir8(dir)).filter((f) => f.endsWith(".encrypted.json"));
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 = join18(overridesDir, "..", "encryption-key.json");
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 readFile18(join18(abDataDir, "environments.json"), "utf-8"));
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 readFile18(join18(abDataDir, "services.json"), "utf-8"));
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(join18(overridesDir, file), sodiumKey, sodium);
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(join18(abDataDir, "environment-overrides.json"), flatOverrides);
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(join18(overridesDir, file), sodiumKey, sodium);
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(join18(abDataDir, "environments.json"), envsRaw);
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 rename14(overridesDir, migratedDir);
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 join19 } from "path";
14062
- import { stat as stat8 } from "fs/promises";
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 stat8(join19(dataDir, ENCRYPTED_KEY_FILENAME2));
14679
+ await stat10(join22(dataDir, ENCRYPTED_KEY_FILENAME2));
14072
14680
  hasEncFile = true;
14073
14681
  } catch {
14074
14682
  }
14075
14683
  try {
14076
- await stat8(join19(dataDir, VERIFY_FILENAME2));
14684
+ await stat10(join22(dataDir, VERIFY_FILENAME2));
14077
14685
  hasVerify = true;
14078
14686
  } catch {
14079
14687
  }
14080
14688
  try {
14081
- await stat8(join19(dataDir, "projects.json"));
14689
+ await stat10(join22(dataDir, "projects.json"));
14082
14690
  hasData = true;
14083
14691
  } catch {
14084
14692
  }
14085
14693
  try {
14086
- await stat8(join19(dataDir, "project.json"));
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 = resolve5(env.DATA_DIR);
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((resolve6) => wss.close(() => resolve6())),
15130
- new Promise((resolve6) => setTimeout(resolve6, 2e3))
15738
+ new Promise((resolve7) => wss.close(() => resolve7())),
15739
+ new Promise((resolve7) => setTimeout(resolve7, 2e3))
15131
15740
  ]);
15132
15741
  await Promise.race([
15133
- new Promise((resolve6) => server.close(() => resolve6())),
15134
- new Promise((resolve6) => setTimeout(resolve6, 2e3))
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((resolve6) => {
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
- resolve6(input);
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((resolve6, reject) => {
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
- resolve6();
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-ZKQETCHR.js.map
15908
+ //# sourceMappingURL=src-5QPLS5TQ.js.map