@runeya/runeya 2.0.44 → 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 (328) 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-NJJMESIY.js → chunk-5PRQBES2.js} +288 -72
  5. package/chunk-5PRQBES2.js.map +1 -0
  6. package/index.js +1 -1
  7. package/package.json +1 -1
  8. package/{src-7CJJBMY3.js → src-5QPLS5TQ.js} +1254 -483
  9. package/src-5QPLS5TQ.js.map +1 -0
  10. package/web/assets/AgentScalarChatInterface.vue-Z3BLbola.js +13 -0
  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-BBifPBJu.css → AppLayout-D1bdzemo.css} +1 -1
  16. package/web/assets/AuditPage-zfim5OLa.js +1 -0
  17. package/web/assets/CloudAccountPage-BD-L27Wl.js +1 -0
  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-ByzcpJV7.js +1 -0
  22. package/web/assets/{EditorIcon-CNesALrK.js → EditorIcon-jk0Nz8Mr.js} +2 -2
  23. package/web/assets/{EnvironmentForm-vcLpp9mR.js → EnvironmentForm-BH4EMxM6.js} +2 -2
  24. package/web/assets/EnvironmentFormPage-Bwjr8SsV.js +1 -0
  25. package/web/assets/EnvironmentListPage-BgqHe-4p.js +1 -0
  26. package/web/assets/EnvironmentListPage-BwgJscFQ.css +1 -0
  27. package/web/assets/EnvironmentSelector-CBh21uuY.js +1 -0
  28. package/web/assets/KanbanBoardPage-Dg0Od24D.js +4 -0
  29. package/web/assets/KanbanListPage-C6bsD-gJ.js +1 -0
  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-BhaSYgvw.js +3 -0
  35. package/web/assets/ProcessControls-lDF1qNIM.js +1 -0
  36. package/web/assets/ProcessDetailPage-DwaApuHs.js +1 -0
  37. package/web/assets/{ProcessMetrics-dy3TGmKO.js → ProcessMetrics-rGmADRtS.js} +2 -2
  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-CndKGKYR.js +2 -0
  43. package/web/assets/ScenarioEditorPage-DU7tb4M-.js +14 -0
  44. package/web/assets/SendToChatDialog-CCRCitBB.js +1 -0
  45. package/web/assets/ServiceControlsBar-BjyMYoiN.js +1 -0
  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-BZM3WWqc.js +2062 -0
  51. package/web/assets/UnlockPage-BcZ2vL57.js +1 -0
  52. package/web/assets/UnlockPage-DE_HJy8h.css +1 -0
  53. package/web/assets/VariablesTable-0Gz2ffiL.js +1 -0
  54. package/web/assets/VaultMigrationPage-BGJ2PP-7.css +1 -0
  55. package/web/assets/VaultMigrationPage-BxTBDL19.js +1 -0
  56. package/web/assets/VersionBadge-DvsaGqvh.js +1 -0
  57. package/web/assets/WikiPage-DkEQXkzp.js +1 -0
  58. package/web/assets/WikiView-e81ejcA8.js +1 -0
  59. package/web/assets/add-scalar-classes-Bh8ghfnQ.js +387 -0
  60. package/web/assets/auth-errors-C1B7h06f.js +1 -0
  61. package/web/assets/{autocomplete-egYkKgXN.js → autocomplete-CDWkaHAi.js} +4 -4
  62. package/web/assets/{basic-shape-diamond.svg-B4ql31Pl.js → basic-shape-diamond.svg-7zqPzGW0.js} +1 -1
  63. package/web/assets/{basic-shape-hexagon.svg-Dse435xU.js → basic-shape-hexagon.svg-DQwxqMd2.js} +1 -1
  64. package/web/assets/{basic-shape-primary-circle-ellipse-round.svg-Ca80MM2U.js → basic-shape-primary-circle-ellipse-round.svg-Bqm796vK.js} +1 -1
  65. package/web/assets/{basic-shape-primary-square-rectangle.svg-BzMOacCe.js → basic-shape-primary-square-rectangle.svg-D7eyO04i.js} +1 -1
  66. package/web/assets/{basic-shape-shield.svg-CDpbzQoc.js → basic-shape-shield.svg-Bnc_pFVA.js} +1 -1
  67. package/web/assets/{browser-CASFdUlr.js → browser-DeXg6zUY.js} +1 -1
  68. package/web/assets/{browser-B6yA2X2D.js → browser-LxxhMX-Z.js} +1 -1
  69. package/web/assets/card-RaXQSIfv.js +32 -0
  70. package/web/assets/{checkbox-DX42-aCG.js → checkbox-DnpUXs7B.js} +3 -3
  71. package/web/assets/{chip-CDVNzEoh.js → chip-0f0r_-zR.js} +2 -2
  72. package/web/assets/{client-wwEU7lJc.js → client-CTzGkOjO.js} +2 -2
  73. package/web/assets/cloud-chat.store-D2oJ_DFn.js +1 -0
  74. package/web/assets/cloud-session-BLn7b3ke.js +1 -0
  75. package/web/assets/column-DqAehgUU.js +907 -0
  76. package/web/assets/{computer-device-desktop-monitor.svg-QvdUlZtb.js → computer-device-desktop-monitor.svg-k2x9Oiiu.js} +1 -1
  77. package/web/assets/{computer-device-desktop.svg-C9Sr1yFe.js → computer-device-desktop.svg-CyFljvrh.js} +1 -1
  78. package/web/assets/{computer-device-laptop.svg-CQbvy4qG.js → computer-device-laptop.svg-ZKpV0gtW.js} +1 -1
  79. package/web/assets/{computer-device-mobile-phone-android-samsung-back.svg-DRwNxxcl.js → computer-device-mobile-phone-android-samsung-back.svg-BkRkRpCh.js} +1 -1
  80. package/web/assets/{computer-device-mobile-phone-android-samsung.svg-DZiXwi21.js → computer-device-mobile-phone-android-samsung.svg-gpUXFo9R.js} +1 -1
  81. package/web/assets/{computer-device-mobile-phone-iphone-x-back.svg-D1eMrnSS.js → computer-device-mobile-phone-iphone-x-back.svg-CJwr0WJ3.js} +1 -1
  82. package/web/assets/{computer-device-mobile-phone-iphone-x.svg-MiK72cdg.js → computer-device-mobile-phone-iphone-x.svg-BsPF_njN.js} +1 -1
  83. package/web/assets/{computer-device-mobile-tablet-touch.svg-DkPhnoi4.js → computer-device-mobile-tablet-touch.svg-DON7qRSP.js} +1 -1
  84. package/web/assets/{computer-device-mobile-tablet.svg-CDuR2wiu.js → computer-device-mobile-tablet.svg-BPnT3MvS.js} +1 -1
  85. package/web/assets/{computer-device-network-ethernet-cat6.svg-BUBM3jji.js → computer-device-network-ethernet-cat6.svg-Bpjv_2ub.js} +1 -1
  86. package/web/assets/{computer-device-network-lan-www.svg-28lu85cP.js → computer-device-network-lan-www.svg-BhXBIyrf.js} +1 -1
  87. package/web/assets/{computer-device-network-wifi-connection.svg-BGEvw9iY.js → computer-device-network-wifi-connection.svg-DUaA1GcU.js} +1 -1
  88. package/web/assets/{computer-device-network-wifi-router.svg-D8xYZAWK.js → computer-device-network-wifi-router.svg-k_DqQho1.js} +1 -1
  89. package/web/assets/{cssMode-Dvq-MecZ.js → cssMode-CfVTS8OM.js} +1 -1
  90. package/web/assets/{divider-B5VXYGmb.js → divider-C1huit3M.js} +2 -2
  91. package/web/assets/{ecology-science-erlenmeyer-flask.svg-Ct1P3ToA.js → ecology-science-erlenmeyer-flask.svg-D7pbgLTT.js} +1 -1
  92. package/web/assets/{fieldset-CIscNGD5.js → fieldset-BVRjgmg4.js} +2 -2
  93. package/web/assets/{freemarker2-CqFs1Djg.js → freemarker2-DsY_gjrP.js} +1 -1
  94. package/web/assets/{handlebars-i2b7C1iO.js → handlebars-mh1HrGq7.js} +1 -1
  95. package/web/assets/{html-BBmJW8Cg.js → html-BsaAhzOa.js} +1 -1
  96. package/web/assets/{htmlMode-DPAh_Y5y.js → htmlMode-pIhI3cAi.js} +1 -1
  97. package/web/assets/{image-flash-lightning.svg-BSAfy_cZ.js → image-flash-lightning.svg-DDbhd-eu.js} +1 -1
  98. package/web/assets/{image-picture-flower.svg-4AEEapQs.js → image-picture-flower.svg-Bbx1pu02.js} +1 -1
  99. package/web/assets/{index-BqCmf99K.css → index-D7XqcLhE.css} +1 -1
  100. package/web/assets/{index-Br0Stb0u.js → index-Do0zA8cN.js} +291 -291
  101. package/web/assets/{interface-alert-exclamation-diamond.svg-DclDPUml.js → interface-alert-exclamation-diamond.svg-DUJeN1hs.js} +1 -1
  102. package/web/assets/{interface-alert-exclamation-triangle-warning.svg-AQlrdI6-.js → interface-alert-exclamation-triangle-warning.svg-5y99yZSY.js} +1 -1
  103. package/web/assets/{interface-alert-information-circle.svg-C70b_ORM.js → interface-alert-information-circle.svg-DPGP01ZU.js} +1 -1
  104. package/web/assets/{interface-award-crown.svg-D-uMX_D5.js → interface-award-crown.svg-GDQ8ZRUh.js} +1 -1
  105. package/web/assets/{interface-bookmark.svg-DdNPAQF4.js → interface-bookmark-tag.svg-D1NUMLB5.js} +1 -1
  106. package/web/assets/{interface-bookmark-tag.svg-BBO8UdsI.js → interface-bookmark.svg-CTZlXD4R.js} +1 -1
  107. package/web/assets/{interface-calendar-date-one.svg-C5m8QsAn.js → interface-calendar-date-one.svg-DXo4raY8.js} +1 -1
  108. package/web/assets/{interface-content-book-open-pages.svg-D9gk_nRv.js → interface-content-book-open-pages.svg-DTsCVw_D.js} +1 -1
  109. package/web/assets/{interface-content-book-page.svg-z_oQ-Kg8.js → interface-content-book-page.svg-w9azHLUe.js} +1 -1
  110. package/web/assets/{interface-content-file.svg-CLt_X895.js → interface-content-file.svg-69CCnbNb.js} +1 -1
  111. package/web/assets/{interface-content-folder.svg-BW_H9jDi.js → interface-content-folder.svg-85PnJNaK.js} +1 -1
  112. package/web/assets/interface-content-folder.svg-Db3hJMdF.js +1 -0
  113. package/web/assets/{interface-copy-clipboard.svg-D89FD0hT.js → interface-copy-clipboard.svg-B0AfVljN.js} +1 -1
  114. package/web/assets/{interface-edit-attachment.svg-CSax0FWg.js → interface-edit-attachment.svg-mFLLqqcw.js} +1 -1
  115. package/web/assets/{interface-edit-binocular.svg-C1t3PxkA.js → interface-edit-binocular.svg-CHzgny3z.js} +1 -1
  116. package/web/assets/{interface-edit-magic-wand.svg-P-HVaqcv.js → interface-edit-magic-wand.svg-BbJg9LMT.js} +1 -1
  117. package/web/assets/{interface-edit-tool-paint-roller.svg-BCd-B5Li.js → interface-edit-tool-paint-roller.svg-YKE28Xpc.js} +1 -1
  118. package/web/assets/{interface-edit-tool-pencil.svg-Dy_8C148.js → interface-edit-tool-pencil.svg-Cy12MfEi.js} +1 -1
  119. package/web/assets/{interface-favorite-award.svg-BOUSPQD8.js → interface-favorite-award.svg-COiLJu4w.js} +1 -1
  120. package/web/assets/{interface-favorite-flag.svg-yj88TNA3.js → interface-favorite-flag.svg-C0NtaPD_.js} +1 -1
  121. package/web/assets/{interface-favorite-heart.svg-CZCH_6x9.js → interface-favorite-heart.svg-CcKJOjOh.js} +1 -1
  122. package/web/assets/{interface-favorite-star.svg-CTyJilUu.js → interface-favorite-star.svg-C3vRaRtM.js} +1 -1
  123. package/web/assets/{interface-favorite-stars-sparkles.svg-BOBjnmtV.js → interface-favorite-stars-sparkles.svg-DN9WOkFs.js} +1 -1
  124. package/web/assets/{interface-hierarchy-flowchart.svg-B0S-kTTJ.js → interface-hierarchy-flowchart.svg-CffgUWNS.js} +1 -1
  125. package/web/assets/{interface-home-house.svg-Dku5UxpT.js → interface-home-house.svg-B9c4N9Fi.js} +1 -1
  126. package/web/assets/{interface-hyperlink.svg-CJwD4AsW.js → interface-hyperlink.svg-B8dXB276.js} +1 -1
  127. package/web/assets/{interface-lighting-brightness.svg-DwLV7f-n.js → interface-lighting-brightness.svg-BjFVAOW3.js} +1 -1
  128. package/web/assets/{interface-lock-closed.svg-9XB_WC1w.js → interface-lock-closed.svg-DwNcKmwu.js} +1 -1
  129. package/web/assets/{interface-lock-open-unlock.svg-DppiuvYV.js → interface-lock-open-unlock.svg-BXZeKlHt.js} +1 -1
  130. package/web/assets/{interface-login-key.svg-C4pLwiam.js → interface-login-key.svg-CQnZGVMJ.js} +1 -1
  131. package/web/assets/{interface-search.svg-BfLZwjYy.js → interface-search.svg-CXre9Ja4.js} +1 -1
  132. package/web/assets/{interface-setting-cog.svg-Cv8hVyGq.js → interface-setting-cog.svg-CGK2QxqV.js} +1 -1
  133. package/web/assets/{interface-share-megaphone-bullhorn.svg-BVlc-dhY.js → interface-share-megaphone-bullhorn.svg-BcdTvuin.js} +1 -1
  134. package/web/assets/{interface-share-rocket.svg-ChHMNOi9.js → interface-share-rocket.svg-DIvKBIQX.js} +1 -1
  135. package/web/assets/{interface-share-satellite.svg-Bh7sMl8A.js → interface-share-satellite.svg-DNbqAorz.js} +1 -1
  136. package/web/assets/{interface-share-space-ship.svg-CcQ-3-tc.js → interface-share-space-ship.svg-Biobo33q.js} +1 -1
  137. package/web/assets/{interface-share.svg-CCg2aMOy.js → interface-share.svg-DWjt5rBb.js} +1 -1
  138. package/web/assets/{interface-signal-square.svg-CU31YeuE.js → interface-signal-square.svg--hmv5CmE.js} +1 -1
  139. package/web/assets/{interface-time-clock-circle.svg-BUUuMbzp.js → interface-time-clock-circle.svg-C1GnMIQD.js} +1 -1
  140. package/web/assets/{interface-time-hour-glass.svg-W7cfslag.js → interface-time-hour-glass.svg-BCRsHIbm.js} +1 -1
  141. package/web/assets/{interface-users-multiple.svg-DjyQMEmj.js → interface-users-multiple.svg-CUpeUldF.js} +1 -1
  142. package/web/assets/{interface-weather-moon.svg-C2sv5n1P.js → interface-weather-moon.svg-QXMY6B27.js} +1 -1
  143. package/web/assets/{javascript-D-ps7-Fj.js → javascript-DKeyayKI.js} +1 -1
  144. package/web/assets/{jsonMode-C8spHQzS.js → jsonMode-I9zemAn2.js} +1 -1
  145. package/web/assets/{liquid-DqSbumgt.js → liquid-xEdzGxP1.js} +1 -1
  146. package/web/assets/{lspLanguageFeatures-CY8c9PkF.js → lspLanguageFeatures-DOVjCuiu.js} +1 -1
  147. package/web/assets/{mail-chat-bubble-square.svg-C3X6jNwQ.js → mail-chat-bubble-square.svg-CR8byO78.js} +1 -1
  148. package/web/assets/{mail-send-email-paper-airplane.svg-DAIGQOyP.js → mail-send-email-paper-airplane.svg-B0cIMUKH.js} +1 -1
  149. package/web/assets/{mail-send-envelope.svg-CA0S-i50.js → mail-send-envelope.svg-El4e7bXy.js} +1 -1
  150. package/web/assets/{mdx-DvZHY6Gn.js → mdx-AWoH37r-.js} +1 -1
  151. package/web/assets/{message-Cj-E8qHk.js → message-CATQqbP_.js} +2 -2
  152. package/web/assets/{minus-Bk1xGw-l.js → minus-Sd1wO9DO.js} +2 -2
  153. package/web/assets/modal-BbDElcmR.js +1 -0
  154. package/web/assets/modal-ZXFHQ6vP.js +5 -0
  155. package/web/assets/{money-cashier-receipt.svg-TBP6hXoT.js → money-cashier-receipt.svg-BIEGQvlX.js} +1 -1
  156. package/web/assets/{money-currency-dollar-pay.svg-DYwjJWCD.js → money-currency-dollar-pay.svg-BgCHAYq5.js} +1 -1
  157. package/web/assets/{money-graph-arrow-increase.svg-BR1Gr97f.js → money-graph-arrow-increase.svg-q6aDu3og.js} +1 -1
  158. package/web/assets/{money-graph-bar-chart-increase.svg-BNAoJq2R.js → money-graph-bar-chart-increase.svg-mRvG_dWg.js} +1 -1
  159. package/web/assets/multiselect-BK7hJt7W.js +251 -0
  160. package/web/assets/{nature-ecology-leaf.svg-CHJE8I4R.js → nature-ecology-leaf.svg-DdhvC8mq.js} +1 -1
  161. package/web/assets/{password-tS13MOUm.js → password-Dnf9oU5a.js} +4 -4
  162. package/web/assets/{phone-telephone.svg-CJQLiuXQ.js → phone-telephone.svg-CjbyxKZA.js} +1 -1
  163. package/web/assets/{plus-CB_mX6Lf.js → plus-jYeRYaw9.js} +6 -6
  164. package/web/assets/process.store-C5LXrPXo.js +1 -0
  165. package/web/assets/{programming-bug.svg-BXjb-M36.js → programming-bug.svg-DvDCWw4P.js} +1 -1
  166. package/web/assets/{programming-cloud.svg-gt7FIwaa.js → programming-cloud.svg-2prWAqB7.js} +1 -1
  167. package/web/assets/{programming-computer-database-server.svg-BtO4lpAh.js → programming-computer-database-server.svg-CT7k5d_p.js} +1 -1
  168. package/web/assets/{programming-computer-database.svg-DQ-dZqw4.js → programming-computer-database.svg-Dm7fpvII.js} +1 -1
  169. package/web/assets/{programming-module-four-layout.svg-laiWKIf7.js → programming-module-four-layout.svg-DBv6QvEO.js} +1 -1
  170. package/web/assets/{programming-module-three.svg--oNt7CzW.js → programming-module-three.svg-BnP96bPJ.js} +1 -1
  171. package/web/assets/{programming-module.svg-C9Ai98wU.js → programming-module.svg-Bdr4ZdZO.js} +1 -1
  172. package/web/assets/{programming-script-code.svg-DhbU3qfH.js → programming-script-code.svg-DIcr6sF-.js} +1 -1
  173. package/web/assets/{python-BQIuDGub.js → python-C554d-pk.js} +1 -1
  174. package/web/assets/{razor-C_wqRSVy.js → razor-BvOiTjBp.js} +1 -1
  175. package/web/assets/router-CCEIu6U5.js +1 -0
  176. package/web/assets/settings.store-DISlY-48.js +1 -0
  177. package/web/assets/{shopping-cart.svg-D-mwPXiz.js → shopping-cart.svg-DoCVSOin.js} +1 -1
  178. package/web/assets/{shopping-gift-present.svg-CplnpU1q.js → shopping-gift-present.svg-CgNqOSQr.js} +1 -1
  179. package/web/assets/{shopping-shipping-box-parcel-package.svg-D0k-fVoU.js → shopping-shipping-box-parcel-package.svg-YkVPEp7J.js} +1 -1
  180. package/web/assets/src-Cvt5pswj.js +1 -0
  181. package/web/assets/{src-D0-roPyX.js → src-DKVQlKvZ.js} +33 -33
  182. package/web/assets/{tabpanel-BXlQXZ2Q.js → tabpanel-DFoLVdFR.js} +3 -3
  183. package/web/assets/{tag-new-circle.svg-msSLdpvt.js → tag-new-circle.svg-BeUe12X2.js} +1 -1
  184. package/web/assets/{travel-map-earth-globe.svg-BfNt3fj_.js → travel-map-earth-globe.svg-B1I1Ed-v.js} +1 -1
  185. package/web/assets/trpc-CVPkysoa.js +1 -0
  186. package/web/assets/{tsMode-B-iW-fA7.js → tsMode-BPD-0Vep.js} +1 -1
  187. package/web/assets/{typescript-X0FI4nNI.js → typescript-Q2uKnndS.js} +1 -1
  188. package/web/assets/useAiChatDrawer-B70XYuU8.js +1 -0
  189. package/web/assets/useCloudOrg-CZ68epfW.js +1 -0
  190. package/web/assets/useProjectCatalog-Kh_sHbSD.js +1 -0
  191. package/web/assets/{xml-BGZfBXt_.js → xml-B0CfAsFk.js} +1 -1
  192. package/web/assets/{yaml-Bu_QXl8g.js → yaml-B3zRI6AS.js} +1 -1
  193. package/web/index.html +2 -2
  194. package/agent-manager-2I47P7CA.js +0 -7
  195. package/chunk-NJJMESIY.js.map +0 -1
  196. package/src-7CJJBMY3.js.map +0 -1
  197. package/web/assets/AgentScalarChatInterface.vue-DJWCM29h.js +0 -13
  198. package/web/assets/AgentTerminalPage-CQxk6jVs.css +0 -1
  199. package/web/assets/AgentTerminalPage-DStCvPsn.js +0 -1
  200. package/web/assets/AiChatPage-B74k4w7z.js +0 -1
  201. package/web/assets/AppLayout-Crr8vrnA.js +0 -1
  202. package/web/assets/AuditPage-ya7Q_-y8.js +0 -1
  203. package/web/assets/CloudAccountPage--zPhBcyX.js +0 -1
  204. package/web/assets/CloudAccountPage-BsXQlx4x.js +0 -1
  205. package/web/assets/CloudMessagingPage-DbhpPX4q.css +0 -1
  206. package/web/assets/CloudMessagingPage-sXT8SLY4.js +0 -2
  207. package/web/assets/DashboardPage-y4tWdya9.js +0 -1
  208. package/web/assets/EnvironmentFormPage-BSeIsEH-.js +0 -1
  209. package/web/assets/EnvironmentListPage-BDqcug66.css +0 -1
  210. package/web/assets/EnvironmentListPage-DtSTzbmv.js +0 -1
  211. package/web/assets/EnvironmentSelector-Dbe1Ilq8.js +0 -1
  212. package/web/assets/KanbanBoardPage-PkAwHbvp.js +0 -4
  213. package/web/assets/KanbanListPage-BphN58qM.js +0 -1
  214. package/web/assets/LaunchProjectPage-ClVBkFsC.js +0 -1
  215. package/web/assets/LaunchProjectPage-CxXC2U3O.css +0 -1
  216. package/web/assets/LaunchServicesPage-BAvD32G1.css +0 -1
  217. package/web/assets/LaunchServicesPage-C4C8l5wP.js +0 -1
  218. package/web/assets/LogViewer-DeWk02W9.js +0 -3
  219. package/web/assets/ProcessControls-uUzThwLe.js +0 -1
  220. package/web/assets/ProcessDetailPage-Dj9-KZqI.js +0 -1
  221. package/web/assets/ProjectDetailPage-B42imx75.css +0 -1
  222. package/web/assets/ProjectDetailPage-Prcd6a6y.js +0 -1
  223. package/web/assets/ProjectListPage-BGU6SKAr.css +0 -1
  224. package/web/assets/ProjectListPage-C3-QvMAf.js +0 -1
  225. package/web/assets/RandomJobTestPage-B5USJ2CQ.js +0 -2
  226. package/web/assets/ScenarioEditorPage-D0XY_WOp.js +0 -14
  227. package/web/assets/SendToChatDialog-BSa5s8PE.js +0 -1
  228. package/web/assets/ServiceControlsBar-CRVx5mMr.js +0 -1
  229. package/web/assets/ServiceDetailPage-DzxAxNdH.js +0 -2
  230. package/web/assets/ServiceFormPage-D31sr_k0.css +0 -1
  231. package/web/assets/ServiceFormPage-_3cU1hqt.js +0 -1
  232. package/web/assets/SettingsPage-B0mpjswp.js +0 -2062
  233. package/web/assets/UnlockPage-DAvZd_hY.js +0 -1
  234. package/web/assets/UnlockPage-cWvMhfyi.css +0 -1
  235. package/web/assets/VariablesTable-CoeTpHQw.js +0 -1
  236. package/web/assets/VersionBadge-D_oFrbj6.js +0 -1
  237. package/web/assets/WikiPage-HEXd9iFN.js +0 -1
  238. package/web/assets/WikiView-D_gNl0pw.js +0 -1
  239. package/web/assets/add-scalar-classes-B5XCUsCH.js +0 -387
  240. package/web/assets/auth-errors-C44F3O24.js +0 -1
  241. package/web/assets/card-DLRYSKgj.js +0 -32
  242. package/web/assets/cloud-chat.store-2Mksi6iL.js +0 -1
  243. package/web/assets/cloud-session-C0DzgONu.js +0 -1
  244. package/web/assets/column-TZbNKWtE.js +0 -907
  245. package/web/assets/interface-content-folder.svg-BJQbuSNW.js +0 -1
  246. package/web/assets/modal-Bi7DAFyz.js +0 -1
  247. package/web/assets/modal-BtlOliEo.js +0 -5
  248. package/web/assets/multiselect-DI4UWQQc.js +0 -251
  249. package/web/assets/process.store-DCXMZqHz.js +0 -1
  250. package/web/assets/router-B5tVgvg3.js +0 -1
  251. package/web/assets/settings.store-p3x3oG-N.js +0 -1
  252. package/web/assets/src-B_ytlQOt.js +0 -1
  253. package/web/assets/trpc--zmPP0fK.js +0 -1
  254. package/web/assets/useAiChatDrawer-CmZm2O_C.js +0 -1
  255. package/web/assets/useCloudOrg-BsMHgwHL.js +0 -1
  256. /package/{agent-manager-2I47P7CA.js.map → agent-manager-JJYZDLNB.js.map} +0 -0
  257. /package/web/assets/{abap-B8xnHZJW.js → abap-DMmvWZUy.js} +0 -0
  258. /package/web/assets/{apex-B69W3sGq.js → apex-D89PiTzU.js} +0 -0
  259. /package/web/assets/{azcli-Eaai8vgm.js → azcli-ChnjTTA5.js} +0 -0
  260. /package/web/assets/{bat-CCyh2P9V.js → bat-uLzQ_FhK.js} +0 -0
  261. /package/web/assets/{bicep-CyAc2Run.js → bicep-CMOK7Q1v.js} +0 -0
  262. /package/web/assets/{cameligo-CJVuMDnv.js → cameligo-D1AyojFH.js} +0 -0
  263. /package/web/assets/{clojure-DCWtDuXT.js → clojure-D9JoUgXn.js} +0 -0
  264. /package/web/assets/{coffee-93zwCqZu.js → coffee-D-5sSToL.js} +0 -0
  265. /package/web/assets/{cpp-t1qXcBbB.js → cpp-BqT_t_y9.js} +0 -0
  266. /package/web/assets/{csharp-Bu0-wvSx.js → csharp-CuToYI91.js} +0 -0
  267. /package/web/assets/{csp-AaiYnRi_.js → csp-CffcxvlO.js} +0 -0
  268. /package/web/assets/{css-BS-T3bCT.js → css-CUD0biQW.js} +0 -0
  269. /package/web/assets/{cypher-QYdwWDet.js → cypher-ByyWL_9U.js} +0 -0
  270. /package/web/assets/{dart-DRQfRz5u.js → dart-B__ipcUn.js} +0 -0
  271. /package/web/assets/{dockerfile-Cf422HaY.js → dockerfile-DS-_8bWw.js} +0 -0
  272. /package/web/assets/{ecl-DKmCxEZC.js → ecl-DWsNCBUj.js} +0 -0
  273. /package/web/assets/{elixir-DXxuFQwb.js → elixir-OfjEy-3g.js} +0 -0
  274. /package/web/assets/{flow9-BhnEDmps.js → flow9-BPX22hxS.js} +0 -0
  275. /package/web/assets/{fsharp-BcdMpySh.js → fsharp-CVJcfIUn.js} +0 -0
  276. /package/web/assets/{go-CUNzU43j.js → go-CfRHZ3yg.js} +0 -0
  277. /package/web/assets/{graphql-0GEdoWdT.js → graphql-BZaO3ttK.js} +0 -0
  278. /package/web/assets/{hcl-7aT0BoBu.js → hcl-BF9-qUku.js} +0 -0
  279. /package/web/assets/{ini-C2uC_Kkd.js → ini-BUvw6xhr.js} +0 -0
  280. /package/web/assets/{java-BGh_orDD.js → java-BZ5BNd2x.js} +0 -0
  281. /package/web/assets/{julia-BMkYL7l7.js → julia-DN38cgYT.js} +0 -0
  282. /package/web/assets/{kotlin-CaStfl8T.js → kotlin-CjEJKT8q.js} +0 -0
  283. /package/web/assets/{less-CpP708lq.js → less-DMEH_Nwq.js} +0 -0
  284. /package/web/assets/{lexon-n74w-VMa.js → lexon-CKQUX8Ov.js} +0 -0
  285. /package/web/assets/{lua-DV3MQq-H.js → lua-CuWjKLJ0.js} +0 -0
  286. /package/web/assets/{m3-Cclze1bC.js → m3-CEp1bVrM.js} +0 -0
  287. /package/web/assets/{markdown-C8b0Ckdo.js → markdown-DxtQ_puq.js} +0 -0
  288. /package/web/assets/{mips-pWj6zxei.js → mips-Dx7qydpO.js} +0 -0
  289. /package/web/assets/{msdax-DvyywuD0.js → msdax-DpNCaSiO.js} +0 -0
  290. /package/web/assets/{mysql-CZlMFPhf.js → mysql-C3xsBDPP.js} +0 -0
  291. /package/web/assets/{objective-c-Dfzs1cqa.js → objective-c-BbaVHCX1.js} +0 -0
  292. /package/web/assets/{pascal-DnvBS6YC.js → pascal-sDju2faq.js} +0 -0
  293. /package/web/assets/{pascaligo-BqQrNWHm.js → pascaligo-ChCdOH6b.js} +0 -0
  294. /package/web/assets/{perl-PmXI7PaC.js → perl-BCgLFj01.js} +0 -0
  295. /package/web/assets/{pgsql-BSIv6vTg.js → pgsql-DtKby69f.js} +0 -0
  296. /package/web/assets/{php-DFl1Flu9.js → php-NYvR17Qb.js} +0 -0
  297. /package/web/assets/{pla-D85akHEG.js → pla-gSg-RBG0.js} +0 -0
  298. /package/web/assets/{postiats-BENv2q_G.js → postiats-BQxHyDfy.js} +0 -0
  299. /package/web/assets/{powerquery-BFkjDbIZ.js → powerquery-DzDmbVZN.js} +0 -0
  300. /package/web/assets/{powershell-YrWnOFV5.js → powershell-yy4Q5BEQ.js} +0 -0
  301. /package/web/assets/{protobuf-Defuvypk.js → protobuf-B_hIk9q0.js} +0 -0
  302. /package/web/assets/{pug-U5vTkfZW.js → pug-CdeEb1ft.js} +0 -0
  303. /package/web/assets/{qsharp-CYAWcALE.js → qsharp-CiSquEgT.js} +0 -0
  304. /package/web/assets/{r-DfDvKIQI.js → r-D1Ocz1x4.js} +0 -0
  305. /package/web/assets/{redis-CfxXb5P7.js → redis-D42Y12yz.js} +0 -0
  306. /package/web/assets/{redshift-C79hPgxo.js → redshift-CS62MYfG.js} +0 -0
  307. /package/web/assets/{restructuredtext-8wPbRyeC.js → restructuredtext-CKFSMEXw.js} +0 -0
  308. /package/web/assets/{ruby-QxUAWasq.js → ruby-C4gTaeU3.js} +0 -0
  309. /package/web/assets/{rust-HuSf37VN.js → rust-C_f2AiHQ.js} +0 -0
  310. /package/web/assets/{sb-Sv7lt4AB.js → sb-CdNdpP1r.js} +0 -0
  311. /package/web/assets/{scala-DKemzKKU.js → scala-N4EWaFTL.js} +0 -0
  312. /package/web/assets/{scheme-BzHyg2h9.js → scheme-CttIARxx.js} +0 -0
  313. /package/web/assets/{scss-rEyGzHl_.js → scss-v0wtbxaE.js} +0 -0
  314. /package/web/assets/{shell-B0M_8Ipp.js → shell-BJdEWXbc.js} +0 -0
  315. /package/web/assets/{solidity-DBlqOy8H.js → solidity-DKDa59zZ.js} +0 -0
  316. /package/web/assets/{sophia-CvF25coV.js → sophia--Kw-vDpr.js} +0 -0
  317. /package/web/assets/{sparql-Bh-bEONh.js → sparql-BI8jS9Pc.js} +0 -0
  318. /package/web/assets/{sql-b2efFD_k.js → sql-Dz6z--6O.js} +0 -0
  319. /package/web/assets/{st-CZYKif4r.js → st-CT8D05gv.js} +0 -0
  320. /package/web/assets/{swift--G_pPFZi.js → swift-Ayb3KzWx.js} +0 -0
  321. /package/web/assets/{systemverilog-lKrXkwoz.js → systemverilog-BtWJM9O5.js} +0 -0
  322. /package/web/assets/{tcl-BBD-NhPH.js → tcl-BJvUWpbV.js} +0 -0
  323. /package/web/assets/{twig-Cp-9q602.js → twig-c3YaBCU6.js} +0 -0
  324. /package/web/assets/{typespec-CFPGsTQu.js → typespec-gFCWLBoZ.js} +0 -0
  325. /package/web/assets/{uuid-2Kb5PDpq.js → uuid-B3B8HIK9.js} +0 -0
  326. /package/web/assets/{vb-CF5A6m2D.js → vb-CVtfWXV2.js} +0 -0
  327. /package/web/assets/{w3c-keyname-B2B-WOb1.js → w3c-keyname-nn0G3QC2.js} +0 -0
  328. /package/web/assets/{wgsl-DwzoWMFM.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,
@@ -35,11 +36,14 @@ import {
35
36
  clearEncryptionKey,
36
37
  clearPendingLegacyKey,
37
38
  decryptValue,
39
+ decryptValueWith,
38
40
  decryptVariableSlots,
39
41
  encryptValue,
42
+ encryptValueWith,
40
43
  encryptVariableSlots,
41
44
  ensureEncryptionKey,
42
45
  ensureLocalVault,
46
+ ensureProjectsLoaded,
43
47
  env,
44
48
  environmentStore,
45
49
  extractEncryptedVariableSlots,
@@ -54,6 +58,7 @@ import {
54
58
  keyRoot,
55
59
  keyedFilePath,
56
60
  legacyMachineFilePath,
61
+ legacyVaultRoot,
57
62
  listCloudVaults,
58
63
  loadFromEnv,
59
64
  localVaultRoot,
@@ -61,10 +66,13 @@ import {
61
66
  machineRoot,
62
67
  nativeParsers,
63
68
  orgVaultRoot,
69
+ projectKey,
64
70
  projectVaultRoot,
65
71
  readEntity,
72
+ readOrgClouds,
73
+ refOfProjectAmong,
66
74
  registerProjectLoader,
67
- rootOfProject,
75
+ rootOfProjectAmong,
68
76
  serviceStore,
69
77
  setEncryptionKey,
70
78
  setEncryptionKeyInMemory,
@@ -72,16 +80,17 @@ import {
72
80
  setProjectServices,
73
81
  setProjectVault,
74
82
  stableEncryptValue,
83
+ stampOrgCloud,
75
84
  traefikManager,
76
85
  vaultFor,
77
86
  writeEntity
78
- } from "./chunk-NJJMESIY.js";
87
+ } from "./chunk-5PRQBES2.js";
79
88
 
80
89
  // ../server/src/create-server.ts
81
90
  import express from "express";
82
91
  import cors from "cors";
83
92
  import { createServer } from "http";
84
- import { resolve as resolve5 } from "path";
93
+ import { resolve as resolve6 } from "path";
85
94
  import { WebSocketServer } from "ws";
86
95
  import { createExpressMiddleware } from "@trpc/server/adapters/express";
87
96
  import { applyWSSHandler } from "@trpc/server/adapters/ws";
@@ -542,6 +551,12 @@ var LocalProjectExistsError = class extends Error {
542
551
  code = "LOCAL_PROJECT_EXISTS";
543
552
  };
544
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
+ */
545
560
  projects = /* @__PURE__ */ new Map();
546
561
  loaded = false;
547
562
  /** Emits `'saved'` after every persisted mutation, for the cloud sync bridge. */
@@ -559,15 +574,15 @@ var ProjectStore = class {
559
574
  async absorb(p, root) {
560
575
  const projectEnvs = await environmentStore.getByProjectId(p.id).catch(() => []);
561
576
  const defaultEnv = projectEnvs.find((e) => e.default);
562
- this.projects.set(p.id, {
577
+ this.projects.set(projectKey(p), {
563
578
  ...p,
564
579
  environmentIds: p.environmentIds ?? [],
565
580
  groupOrder: p.groupOrder ?? [],
566
581
  // Prefer the env marked default, fall back to first env
567
582
  activeEnvironmentId: defaultEnv?.id ?? p.environmentIds?.[0]
568
583
  });
569
- setProjectVault(p.id, root);
570
- setProjectServices(p.id, p.serviceIds ?? []);
584
+ setProjectVault(p, root);
585
+ setProjectServices(p, p.serviceIds ?? []);
571
586
  }
572
587
  /**
573
588
  * Read every vault: the launch directory, then each organization's projects.
@@ -633,17 +648,40 @@ var ProjectStore = class {
633
648
  for (const { project, root } of cloud) {
634
649
  await mkdir2(root, { recursive: true });
635
650
  await this.writeAtomic(this.getFilePath(root), JSON.stringify(toDiskForm(project), null, 2));
636
- setProjectVault(project.id, root);
651
+ setProjectVault(project, root);
637
652
  }
638
653
  this.events.emit("saved");
639
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
+ }
640
678
  async list() {
641
679
  await this.load();
642
680
  return Array.from(this.projects.values());
643
681
  }
644
- async get(id) {
682
+ async get(target) {
645
683
  await this.load();
646
- return this.projects.get(id) ?? null;
684
+ return this.find(target)?.project ?? null;
647
685
  }
648
686
  async create(input) {
649
687
  await this.load();
@@ -663,16 +701,22 @@ var ProjectStore = class {
663
701
  activeEnvironmentId: input.activeEnvironmentId,
664
702
  groupOrder: input.groupOrder ?? []
665
703
  };
666
- this.projects.set(project.id, project);
667
- setProjectVault(project.id, vaultFor(project).root);
668
- setProjectServices(project.id, project.serviceIds);
704
+ this.projects.set(projectKey(project), project);
705
+ setProjectVault(project, vaultFor(project).root);
706
+ setProjectServices(project, project.serviceIds);
669
707
  await this.save();
670
708
  return project;
671
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
+ */
672
715
  async update(input) {
673
716
  await this.load();
674
- const existing = this.projects.get(input.id);
675
- if (!existing) return null;
717
+ const found = this.find(input);
718
+ if (!found) return null;
719
+ const { key, project: existing } = found;
676
720
  const updated = {
677
721
  ...existing,
678
722
  ...input.name !== void 0 && { name: input.name },
@@ -680,20 +724,20 @@ var ProjectStore = class {
680
724
  ...input.serviceIds !== void 0 && { serviceIds: input.serviceIds },
681
725
  ...input.environmentIds !== void 0 && { environmentIds: input.environmentIds },
682
726
  ...input.activeEnvironmentId !== void 0 && { activeEnvironmentId: input.activeEnvironmentId },
683
- ...input.orgId !== void 0 && { orgId: input.orgId },
684
727
  ...input.groupOrder !== void 0 && { groupOrder: input.groupOrder }
685
728
  };
686
- this.projects.set(input.id, updated);
687
- setProjectVault(updated.id, vaultFor(updated).root);
688
- setProjectServices(updated.id, updated.serviceIds);
729
+ this.projects.set(key, updated);
730
+ setProjectVault(updated, vaultFor(updated).root);
731
+ setProjectServices(updated, updated.serviceIds);
689
732
  await this.save();
690
733
  return updated;
691
734
  }
692
- async delete(id) {
735
+ async delete(target) {
693
736
  await this.load();
694
- const project = this.projects.get(id);
695
- if (!project) return false;
696
- 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);
697
741
  await this.save();
698
742
  await this.discardVault(project);
699
743
  return true;
@@ -707,7 +751,7 @@ var ProjectStore = class {
707
751
  * are only removed from the list.
708
752
  */
709
753
  async discardVault(project) {
710
- forgetProject(project.id);
754
+ forgetProject(project);
711
755
  if (!project.orgId) return;
712
756
  await rm(vaultFor(project).root, { recursive: true, force: true }).catch(() => {
713
757
  });
@@ -720,14 +764,15 @@ var ProjectStore = class {
720
764
  * back on the next save — and, its project being unknown by then, into the
721
765
  * local vault.
722
766
  */
723
- async forgetLocally(projectId) {
767
+ async forgetLocally(target) {
724
768
  await this.load();
725
- const project = this.projects.get(projectId);
726
- if (!project) return;
727
- this.projects.delete(projectId);
728
- 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);
729
774
  await serviceStore.forgetLocally(project.serviceIds ?? []);
730
- await environmentStore.forgetByProject(projectId);
775
+ await environmentStore.forgetByProject(project.id);
731
776
  }
732
777
  /** All projects linked to a given organization (for the sync bridge). */
733
778
  async listByOrg(orgId) {
@@ -756,92 +801,99 @@ var ProjectStore = class {
756
801
  return null;
757
802
  }
758
803
  const incoming = toDiskForm(parsed.data);
759
- const existing = this.projects.get(id);
804
+ const key = projectKey(parsed.data);
805
+ const existing = this.projects.get(key);
760
806
  if (existing) {
761
807
  if (JSON.stringify(toDiskForm(existing)) === JSON.stringify(incoming)) {
762
808
  return { project: existing, created: false };
763
809
  }
764
810
  const updated = { ...incoming, activeEnvironmentId: existing.activeEnvironmentId };
765
- this.projects.set(id, updated);
766
- setProjectVault(id, vaultFor(updated).root);
767
- setProjectServices(id, updated.serviceIds);
811
+ this.projects.set(key, updated);
812
+ setProjectVault(updated, vaultFor(updated).root);
813
+ setProjectServices(updated, updated.serviceIds);
768
814
  await this.save();
769
815
  return { project: updated, created: false };
770
816
  }
771
817
  const project = { ...incoming, activeEnvironmentId: void 0 };
772
- this.projects.set(id, project);
773
- setProjectVault(id, vaultFor(project).root);
774
- setProjectServices(id, project.serviceIds);
818
+ this.projects.set(key, project);
819
+ setProjectVault(project, vaultFor(project).root);
820
+ setProjectServices(project, project.serviceIds);
775
821
  await this.save();
776
822
  return { project, created: true };
777
823
  }
778
824
  /** Delete a project that was removed on another peer. */
779
- async applyRemoteDelete(id) {
825
+ async applyRemoteDelete(target) {
780
826
  await this.load();
781
- const project = this.projects.get(id);
782
- if (!project) return false;
783
- 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);
784
831
  await this.save();
785
832
  await this.discardVault(project);
786
833
  return true;
787
834
  }
788
- async addService(projectId, serviceId) {
835
+ async addService(target, serviceId) {
789
836
  await this.load();
790
- const project = this.projects.get(projectId);
791
- if (!project) return null;
837
+ const found = this.find(target);
838
+ if (!found) return null;
839
+ const { key, project } = found;
792
840
  if (project.serviceIds.includes(serviceId)) return project;
793
841
  const updated = { ...project, serviceIds: [...project.serviceIds, serviceId] };
794
- this.projects.set(projectId, updated);
795
- setProjectServices(projectId, updated.serviceIds);
842
+ this.projects.set(key, updated);
843
+ setProjectServices(updated, updated.serviceIds);
796
844
  await this.save();
797
845
  return updated;
798
846
  }
799
- async removeService(projectId, serviceId) {
847
+ async removeService(target, serviceId) {
800
848
  await this.load();
801
- const project = this.projects.get(projectId);
802
- if (!project) return null;
849
+ const found = this.find(target);
850
+ if (!found) return null;
851
+ const { key, project } = found;
803
852
  const updated = { ...project, serviceIds: project.serviceIds.filter((id) => id !== serviceId) };
804
- this.projects.set(projectId, updated);
805
- setProjectServices(projectId, updated.serviceIds);
853
+ this.projects.set(key, updated);
854
+ setProjectServices(updated, updated.serviceIds);
806
855
  await this.save();
807
856
  return updated;
808
857
  }
809
- async addEnvironment(projectId, environmentId) {
858
+ async addEnvironment(target, environmentId) {
810
859
  await this.load();
811
- const project = this.projects.get(projectId);
812
- if (!project) return null;
860
+ const found = this.find(target);
861
+ if (!found) return null;
862
+ const { key, project } = found;
813
863
  if (project.environmentIds?.includes(environmentId)) return project;
814
864
  const updated = {
815
865
  ...project,
816
866
  environmentIds: [...project.environmentIds ?? [], environmentId]
817
867
  };
818
- this.projects.set(projectId, updated);
868
+ this.projects.set(key, updated);
819
869
  await this.save();
820
870
  return updated;
821
871
  }
822
- async removeEnvironment(projectId, environmentId) {
872
+ async removeEnvironment(target, environmentId) {
823
873
  await this.load();
824
- const project = this.projects.get(projectId);
825
- if (!project) return null;
874
+ const found = this.find(target);
875
+ if (!found) return null;
876
+ const { key, project } = found;
826
877
  const updated = {
827
878
  ...project,
828
879
  environmentIds: (project.environmentIds ?? []).filter((id) => id !== environmentId),
829
880
  // Clear activeEnvironmentId if it was the removed environment
830
881
  ...project.activeEnvironmentId === environmentId && { activeEnvironmentId: void 0 }
831
882
  };
832
- this.projects.set(projectId, updated);
883
+ this.projects.set(key, updated);
833
884
  await this.save();
834
885
  return updated;
835
886
  }
836
- async setActiveEnvironment(projectId, environmentId) {
887
+ async setActiveEnvironment(target, environmentId) {
837
888
  await this.load();
838
- const project = this.projects.get(projectId);
839
- if (!project) return null;
889
+ const found = this.find(target);
890
+ if (!found) return null;
891
+ const { key, project } = found;
840
892
  const updated = {
841
893
  ...project,
842
894
  activeEnvironmentId: environmentId
843
895
  };
844
- this.projects.set(projectId, updated);
896
+ this.projects.set(key, updated);
845
897
  return updated;
846
898
  }
847
899
  };
@@ -873,8 +925,8 @@ var EnvironmentOverridesStore = class {
873
925
  * may simply not be loaded yet.
874
926
  */
875
927
  async rootFor(variableId) {
876
- const projectId = await environmentStore.projectIdOfVariable(variableId);
877
- if (projectId) return rootOfProject(projectId);
928
+ const root = await environmentStore.rootOfVariable(variableId);
929
+ if (root) return root;
878
930
  return this.roots.get(variableId) ?? dirname2(await keyedFilePath(FILENAME2));
879
931
  }
880
932
  /** Where earlier versions kept them: read once, rewritten to their vaults. */
@@ -891,6 +943,7 @@ var EnvironmentOverridesStore = class {
891
943
  if (this.loaded) return;
892
944
  if (!getEncryptionKey()) return;
893
945
  this.loaded = true;
946
+ await ensureProjectsLoaded();
894
947
  const roots = await allVaultRoots();
895
948
  const sources = [
896
949
  this.getLegacyFilePath(),
@@ -1456,13 +1509,98 @@ var AuditService = class {
1456
1509
  };
1457
1510
  var auditService = new AuditService();
1458
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
+
1459
1593
  // ../server/src/trpc/routers/project.ts
1460
1594
  var projectRouter = router({
1461
1595
  list: protectedProcedure.query(async () => {
1462
1596
  return projectStore.list();
1463
1597
  }),
1464
- get: protectedProcedure.input(z2.object({ id: z2.string().max(128) })).query(async ({ input }) => {
1465
- 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);
1466
1604
  if (!project) {
1467
1605
  throw new TRPCError2({ code: "NOT_FOUND", message: `Project ${input.id} not found` });
1468
1606
  }
@@ -1474,8 +1612,8 @@ var projectRouter = router({
1474
1612
  * Environments are intentionally excluded — they carry secret values that
1475
1613
  * stay local.
1476
1614
  */
1477
- export: protectedProcedure.input(z2.object({ id: z2.string().max(128) })).query(async ({ input }) => {
1478
- 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);
1479
1617
  if (!project) {
1480
1618
  throw new TRPCError2({ code: "NOT_FOUND", message: `Project ${input.id} not found` });
1481
1619
  }
@@ -1510,9 +1648,10 @@ var projectRouter = router({
1510
1648
  description: "Default local environment (immutable)",
1511
1649
  parentIds: [],
1512
1650
  immutable: true
1513
- });
1651
+ }, project.orgId);
1514
1652
  const result = await projectStore.update({
1515
1653
  id: project.id,
1654
+ orgId: project.orgId,
1516
1655
  environmentIds: [localEnv.id],
1517
1656
  activeEnvironmentId: localEnv.id
1518
1657
  });
@@ -1536,22 +1675,27 @@ var projectRouter = router({
1536
1675
  await auditService.log({ userId: "local", action: "update", entityType: "project", entityId: input.id });
1537
1676
  return project;
1538
1677
  }),
1539
- delete: protectedProcedure.input(z2.object({ id: z2.string().max(128) })).mutation(async ({ input }) => {
1540
- 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);
1541
1680
  if (!deleted) {
1542
1681
  throw new TRPCError2({ code: "NOT_FOUND", message: `Project ${input.id} not found` });
1543
1682
  }
1544
1683
  await auditService.log({ userId: "local", action: "delete", entityType: "project", entityId: input.id });
1545
1684
  return { success: true };
1546
1685
  }),
1547
- addService: protectedProcedure.input(z2.object({ projectId: z2.string().max(128), serviceId: z2.string().max(128) })).mutation(async ({ input }) => {
1548
- 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);
1549
1693
  if (!project) {
1550
1694
  throw new TRPCError2({ code: "NOT_FOUND", message: `Project ${input.projectId} not found` });
1551
1695
  }
1552
1696
  const service = await serviceStore.get(input.serviceId);
1553
1697
  if (service) {
1554
- const globalEnvs = await environmentStore.listByScope(input.projectId, "global");
1698
+ const globalEnvs = await environmentStore.listByScope(input.projectId, "global", input.orgId);
1555
1699
  for (const globalEnv of globalEnvs) {
1556
1700
  const svcEnv = await environmentStore.create({
1557
1701
  projectId: input.projectId,
@@ -1561,8 +1705,8 @@ var projectRouter = router({
1561
1705
  activateOn: globalEnv.id,
1562
1706
  parentIds: [globalEnv.id],
1563
1707
  variables: {}
1564
- });
1565
- await projectStore.addEnvironment(input.projectId, svcEnv.id);
1708
+ }, input.orgId);
1709
+ await projectStore.addEnvironment(target, svcEnv.id);
1566
1710
  }
1567
1711
  }
1568
1712
  await auditService.log({
@@ -1574,8 +1718,15 @@ var projectRouter = router({
1574
1718
  });
1575
1719
  return project;
1576
1720
  }),
1577
- removeService: protectedProcedure.input(z2.object({ projectId: z2.string().max(128), serviceId: z2.string().max(128) })).mutation(async ({ input }) => {
1578
- 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
+ );
1579
1730
  if (!project) {
1580
1731
  throw new TRPCError2({ code: "NOT_FOUND", message: `Project ${input.projectId} not found` });
1581
1732
  }
@@ -1588,8 +1739,12 @@ var projectRouter = router({
1588
1739
  });
1589
1740
  return project;
1590
1741
  }),
1591
- setActiveEnvironment: protectedProcedure.input(z2.object({ projectId: z2.string().max(128), environmentId: z2.string().max(128) })).mutation(async ({ input }) => {
1592
- 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 });
1593
1748
  if (!project) {
1594
1749
  throw new TRPCError2({ code: "NOT_FOUND", message: `Project ${input.projectId} not found` });
1595
1750
  }
@@ -1618,7 +1773,10 @@ var projectRouter = router({
1618
1773
  } catch {
1619
1774
  }
1620
1775
  }
1621
- 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
+ );
1622
1780
  if (!updated) {
1623
1781
  throw new TRPCError2({ code: "INTERNAL_SERVER_ERROR", message: "Failed to set active environment" });
1624
1782
  }
@@ -1653,6 +1811,53 @@ var projectRouter = router({
1653
1811
  }
1654
1812
  }
1655
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 };
1656
1861
  })
1657
1862
  });
1658
1863
 
@@ -1661,7 +1866,7 @@ import { z as z3 } from "zod";
1661
1866
  import { TRPCError as TRPCError3 } from "@trpc/server";
1662
1867
 
1663
1868
  // ../server/src/services/settings-manager.ts
1664
- 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";
1665
1870
  var SETTINGS_FILE = "settings.json";
1666
1871
  var SETTINGS_PRIVATE_FILE = "settings.private.json";
1667
1872
  var SCHEMA_VERSION_FILE = "schema-version.json";
@@ -1671,21 +1876,21 @@ var SettingsManager = class {
1671
1876
  settingsPrivate = null;
1672
1877
  schemaVersion = null;
1673
1878
  async ensureDir() {
1674
- await mkdir5(machineRoot(), { recursive: true });
1879
+ await mkdir6(machineRoot(), { recursive: true });
1675
1880
  }
1676
1881
  async atomicWrite(filename, data) {
1677
1882
  await this.ensureDir();
1678
1883
  const filePath = machineFilePath(filename);
1679
1884
  const tmpPath = filePath + ".tmp";
1680
- await writeFile5(tmpPath, JSON.stringify(data, null, 2), "utf-8");
1681
- await rename5(tmpPath, filePath);
1682
- 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);
1683
1888
  }
1684
1889
  /** Read the machine file, falling back to the launch directory of old. */
1685
1890
  async readJson(filename) {
1686
1891
  for (const path of [machineFilePath(filename), legacyMachineFilePath(filename)]) {
1687
1892
  try {
1688
- return JSON.parse(await readFile5(path, "utf-8"));
1893
+ return JSON.parse(await readFile6(path, "utf-8"));
1689
1894
  } catch {
1690
1895
  }
1691
1896
  }
@@ -1748,6 +1953,62 @@ var SettingsManager = class {
1748
1953
  };
1749
1954
  var settingsManager = new SettingsManager();
1750
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
+
1751
2012
  // ../server/src/trpc/routers/service.ts
1752
2013
  async function resolveEffectiveAgentId(service) {
1753
2014
  if (service.agentId) return service.agentId;
@@ -1761,6 +2022,10 @@ async function resolveServiceVariables(service) {
1761
2022
  const projects = await projectStore.list();
1762
2023
  const project = projects.find((p) => p.serviceIds.includes(service.id));
1763
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
+ }
1764
2029
  try {
1765
2030
  if (service.parserIds && service.parserIds.length > 0) {
1766
2031
  const settings = await settingsManager.getSettings();
@@ -1972,17 +2237,19 @@ var serviceRouter = router({
1972
2237
  throw new TRPCError3({ code: "BAD_REQUEST", message: "No agent configured for this service" });
1973
2238
  }
1974
2239
  const agentResult = await agentManager.agentQuery(effectiveAgentId, "git.checkCwd", {
1975
- 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 }),
1976
2243
  remote: service.git?.remote,
1977
2244
  checkReachability: input.checkReachability
1978
2245
  });
1979
- const { exists, actualRemote, unreachable, resolvedCwd, unresolvedVars } = agentResult;
2246
+ const { exists: exists2, actualRemote, unreachable, resolvedCwd, unresolvedVars } = agentResult;
1980
2247
  if (unresolvedVars.length > 0) {
1981
2248
  return { exists: false, canClone: false, unresolvedVars, resolvedCwd, gitMismatch: null };
1982
2249
  }
1983
- const canClone = !!service.git?.remote && (!exists || actualRemote === null);
2250
+ const canClone = !!service.git?.remote && (!exists2 || actualRemote === null);
1984
2251
  let gitMismatch = null;
1985
- if (exists && service.git?.remote && !canClone) {
2252
+ if (exists2 && service.git?.remote && !canClone) {
1986
2253
  if (actualRemote === null) {
1987
2254
  gitMismatch = { actual: null, configured: service.git.remote };
1988
2255
  } else if (actualRemote !== service.git.remote) {
@@ -1991,7 +2258,7 @@ var serviceRouter = router({
1991
2258
  gitMismatch = { actual: actualRemote, configured: service.git.remote, unreachable: true };
1992
2259
  }
1993
2260
  }
1994
- return { exists, canClone, unresolvedVars: [], resolvedCwd, gitMismatch };
2261
+ return { exists: exists2, canClone, unresolvedVars: [], resolvedCwd, gitMismatch };
1995
2262
  }),
1996
2263
  cloneRepository: protectedProcedure.input(z3.object({ id: z3.string().max(128) })).mutation(async ({ input }) => {
1997
2264
  const service = await serviceStore.get(input.id);
@@ -2007,7 +2274,9 @@ var serviceRouter = router({
2007
2274
  }
2008
2275
  await agentManager.agentMutation(effectiveAgentId, "git.clone", {
2009
2276
  remote: service.git.remote,
2010
- 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 })
2011
2280
  }, 5 * 60 * 1e3);
2012
2281
  return { success: true };
2013
2282
  })
@@ -2141,13 +2410,13 @@ var agentRouter = router({
2141
2410
  yield { agentId: agent.config.id, status: agent.status };
2142
2411
  }
2143
2412
  const queue = [];
2144
- let resolve6 = null;
2413
+ let resolve7 = null;
2145
2414
  const handler = (event) => {
2146
2415
  if (queue.length >= SUBSCRIPTION_QUEUE_MAX) queue.shift();
2147
2416
  queue.push(event);
2148
- if (resolve6) {
2149
- resolve6();
2150
- resolve6 = null;
2417
+ if (resolve7) {
2418
+ resolve7();
2419
+ resolve7 = null;
2151
2420
  }
2152
2421
  };
2153
2422
  agentManager.on("status", handler);
@@ -2157,7 +2426,7 @@ var agentRouter = router({
2157
2426
  yield queue.shift();
2158
2427
  } else {
2159
2428
  await new Promise((r) => {
2160
- resolve6 = r;
2429
+ resolve7 = r;
2161
2430
  });
2162
2431
  }
2163
2432
  }
@@ -2173,14 +2442,14 @@ import { TRPCError as TRPCError5 } from "@trpc/server";
2173
2442
  var SUBSCRIPTION_QUEUE_MAX2 = LOG_BUFFER_SIZE;
2174
2443
  async function* forwardEvents(eventName, filter) {
2175
2444
  const queue = [];
2176
- let resolve6 = null;
2445
+ let resolve7 = null;
2177
2446
  const handler = (data) => {
2178
2447
  if (filter && !filter(data)) return;
2179
2448
  if (queue.length >= SUBSCRIPTION_QUEUE_MAX2) queue.shift();
2180
2449
  queue.push(data);
2181
- if (resolve6) {
2182
- resolve6();
2183
- resolve6 = null;
2450
+ if (resolve7) {
2451
+ resolve7();
2452
+ resolve7 = null;
2184
2453
  }
2185
2454
  };
2186
2455
  agentManager.on(eventName, handler);
@@ -2190,7 +2459,7 @@ async function* forwardEvents(eventName, filter) {
2190
2459
  yield queue.shift();
2191
2460
  } else {
2192
2461
  await new Promise((r) => {
2193
- resolve6 = r;
2462
+ resolve7 = r;
2194
2463
  });
2195
2464
  }
2196
2465
  }
@@ -2200,7 +2469,7 @@ async function* forwardEvents(eventName, filter) {
2200
2469
  }
2201
2470
  async function* forwardLogBatches(agentId, processId) {
2202
2471
  const queue = [];
2203
- let resolve6 = null;
2472
+ let resolve7 = null;
2204
2473
  const handler = (data) => {
2205
2474
  if (data.agentId !== agentId) return;
2206
2475
  const batch = data.batch;
@@ -2209,9 +2478,9 @@ async function* forwardLogBatches(agentId, processId) {
2209
2478
  if (filtered.length === 0) return;
2210
2479
  if (queue.length >= SUBSCRIPTION_QUEUE_MAX2) queue.shift();
2211
2480
  queue.push(filtered);
2212
- if (resolve6) {
2213
- resolve6();
2214
- resolve6 = null;
2481
+ if (resolve7) {
2482
+ resolve7();
2483
+ resolve7 = null;
2215
2484
  }
2216
2485
  };
2217
2486
  agentManager.on("process:log", handler);
@@ -2221,7 +2490,7 @@ async function* forwardLogBatches(agentId, processId) {
2221
2490
  yield queue.shift();
2222
2491
  } else {
2223
2492
  await new Promise((r) => {
2224
- resolve6 = r;
2493
+ resolve7 = r;
2225
2494
  });
2226
2495
  }
2227
2496
  }
@@ -2402,7 +2671,14 @@ var processRouter = router({
2402
2671
  const result = await agentManager.agentMutation(
2403
2672
  input.agentId,
2404
2673
  "process.executeAdHocCommand",
2405
- { 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
+ },
2406
2682
  AGENT_LONG_FETCH_TIMEOUT
2407
2683
  );
2408
2684
  await auditService.log({
@@ -2579,25 +2855,36 @@ var setupRouter = router({
2579
2855
  import { z as z8 } from "zod";
2580
2856
  import { TRPCError as TRPCError6 } from "@trpc/server";
2581
2857
  var environmentRouter = router({
2582
- list: protectedProcedure.input(z8.object({ projectId: z8.string().max(128) })).query(async ({ input }) => {
2583
- 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);
2584
2863
  }),
2585
2864
  get: protectedProcedure.input(z8.object({ id: z8.string().max(128) })).query(async ({ input }) => {
2586
2865
  const env2 = await environmentStore.get(input.id);
2587
2866
  if (!env2) throw new TRPCError6({ code: "NOT_FOUND", message: `Environment ${input.id} not found` });
2588
2867
  return env2;
2589
2868
  }),
2590
- listByScope: protectedProcedure.input(z8.object({ projectId: z8.string().max(128), scope: z8.enum(["global", "service"]) })).query(async ({ input }) => {
2591
- 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);
2592
2875
  }),
2593
2876
  findServiceEnvironment: protectedProcedure.input(z8.object({ serviceId: z8.string().max(128), globalEnvId: z8.string().max(128) })).query(async ({ input }) => {
2594
2877
  return await environmentStore.findServiceEnvironment(input.serviceId, input.globalEnvId);
2595
2878
  }),
2596
- create: protectedProcedure.input(CreateEnvironmentSchema).mutation(async ({ input }) => {
2597
- const env2 = await environmentStore.create(input);
2598
- 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);
2599
2886
  if (project && !project.environmentIds?.includes(env2.id)) {
2600
- await projectStore.addEnvironment(input.projectId, env2.id);
2887
+ await projectStore.addEnvironment(target, env2.id);
2601
2888
  }
2602
2889
  if (env2.scope === "global" && project) {
2603
2890
  for (const serviceId of project.serviceIds) {
@@ -2618,8 +2905,8 @@ var environmentRouter = router({
2618
2905
  activateOn: env2.id,
2619
2906
  parentIds: [env2.id, ...parentServiceEnvIds],
2620
2907
  variables: {}
2621
- });
2622
- await projectStore.addEnvironment(input.projectId, svcEnv.id);
2908
+ }, orgId);
2909
+ await projectStore.addEnvironment(target, svcEnv.id);
2623
2910
  }
2624
2911
  }
2625
2912
  return env2;
@@ -2630,7 +2917,7 @@ var environmentRouter = router({
2630
2917
  const env2 = await environmentStore.update(input);
2631
2918
  if (!env2) throw new TRPCError6({ code: "NOT_FOUND", message: `Environment ${input.id} not found` });
2632
2919
  if (env2.scope === "global" && input.name && input.name !== existing.name) {
2633
- const project = await projectStore.get(env2.projectId);
2920
+ const project = await projectStore.get(environmentStore.projectRefOf(env2) ?? env2.projectId);
2634
2921
  if (project) {
2635
2922
  const serviceNames = /* @__PURE__ */ new Map();
2636
2923
  for (const serviceId of project.serviceIds) {
@@ -2653,7 +2940,7 @@ var environmentRouter = router({
2653
2940
  }
2654
2941
  const deleted = await environmentStore.delete(input.id);
2655
2942
  if (!deleted) throw new TRPCError6({ code: "INTERNAL_SERVER_ERROR", message: "Failed to delete environment" });
2656
- await projectStore.removeEnvironment(env2.projectId, input.id);
2943
+ await projectStore.removeEnvironment(environmentStore.projectRefOf(env2) ?? env2.projectId, input.id);
2657
2944
  return { success: true };
2658
2945
  }),
2659
2946
  resolve: protectedProcedure.input(z8.object({ environmentId: z8.string().max(128) })).query(async ({ input, ctx }) => {
@@ -2728,11 +3015,12 @@ var environmentRouter = router({
2728
3015
  */
2729
3016
  keyUsages: protectedProcedure.input(z8.object({
2730
3017
  projectId: z8.string().max(128),
3018
+ orgId: z8.string().max(128).nullable().optional(),
2731
3019
  key: z8.string().min(1).max(256),
2732
3020
  /** Environment holding the variable about to be edited — enables inherited-impact detection */
2733
3021
  environmentId: z8.string().max(128).optional()
2734
3022
  })).query(async ({ input }) => {
2735
- const environments = await environmentStore.list(input.projectId);
3023
+ const environments = await environmentStore.list(input.projectId, input.orgId);
2736
3024
  const services = await serviceStore.list();
2737
3025
  const serviceName = (id) => services.find((s) => s.id === id)?.name;
2738
3026
  const escaped = input.key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -2801,7 +3089,6 @@ var environmentRouter = router({
2801
3089
 
2802
3090
  // ../server/src/trpc/routers/settings.ts
2803
3091
  import { promises as dns } from "dns";
2804
- import { resolve } from "path";
2805
3092
  import { z as z9 } from "zod";
2806
3093
  import { TRPCError as TRPCError7 } from "@trpc/server";
2807
3094
  var NotificationPreferencesPatchSchema = NotificationPreferencesSchema.partial().strict();
@@ -2821,9 +3108,12 @@ var settingsRouter = router({
2821
3108
  * Paths the server is running from (working directory + data directory).
2822
3109
  * Read-only, purely informational for the settings UI.
2823
3110
  */
2824
- getPaths: protectedProcedure.query(() => ({
2825
- cwd: process.cwd(),
2826
- 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,
2827
3117
  // Where cloud projects live: their vaults belong to the machine, not to
2828
3118
  // whichever directory the server happened to be started in.
2829
3119
  machineDir: machineRoot()
@@ -3187,7 +3477,7 @@ var settingsRouter = router({
3187
3477
  }
3188
3478
  } catch {
3189
3479
  }
3190
- await new Promise((resolve6) => setTimeout(resolve6, 500));
3480
+ await new Promise((resolve7) => setTimeout(resolve7, 500));
3191
3481
  }
3192
3482
  }),
3193
3483
  /**
@@ -3373,16 +3663,16 @@ var settingsRouter = router({
3373
3663
  import { z as z11 } from "zod";
3374
3664
  import { TRPCError as TRPCError8 } from "@trpc/server";
3375
3665
  import { execFile as execFile2, spawn } from "child_process";
3376
- import { stat as stat2 } from "fs/promises";
3377
- 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";
3378
3668
  import { homedir as homedir2 } from "os";
3379
3669
 
3380
3670
  // ../server/src/services/user-config-manager.ts
3381
- import { readFile as readFile6, writeFile as writeFile6, rename as rename6, mkdir as mkdir6, chmod as chmod5 } from "fs/promises";
3382
- 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";
3383
3673
  import { homedir } from "os";
3384
3674
  import { z as z10 } from "zod";
3385
- var CONFIG_DIR = join4(homedir(), ".runeya");
3675
+ var CONFIG_DIR = join6(homedir(), ".runeya");
3386
3676
  var CONFIG_FILE = "runeya.conf";
3387
3677
  var EditorPreferencesSchema = z10.object({
3388
3678
  default: z10.string().max(64).optional()
@@ -3396,23 +3686,26 @@ var UserConfigSchema = z10.record(
3396
3686
  z10.string().max(128),
3397
3687
  ProjectSettingsSchema
3398
3688
  ).refine((obj) => Object.keys(obj).length <= 100, "Too many projects in config");
3689
+ function configKey(ref) {
3690
+ return projectKey(ref);
3691
+ }
3399
3692
  var UserConfigManager = class {
3400
3693
  config = null;
3401
3694
  async ensureDir() {
3402
- await mkdir6(CONFIG_DIR, { recursive: true });
3695
+ await mkdir7(CONFIG_DIR, { recursive: true });
3403
3696
  }
3404
3697
  async atomicWrite(data) {
3405
3698
  await this.ensureDir();
3406
- const filePath = join4(CONFIG_DIR, CONFIG_FILE);
3699
+ const filePath = join6(CONFIG_DIR, CONFIG_FILE);
3407
3700
  const tmpPath = filePath + ".tmp";
3408
- await writeFile6(tmpPath, JSON.stringify(data, null, 2), "utf-8");
3409
- await rename6(tmpPath, filePath);
3410
- 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);
3411
3704
  }
3412
3705
  async getConfig() {
3413
3706
  if (!this.config) {
3414
3707
  try {
3415
- const raw = await readFile6(join4(CONFIG_DIR, CONFIG_FILE), "utf-8");
3708
+ const raw = await readFile7(join6(CONFIG_DIR, CONFIG_FILE), "utf-8");
3416
3709
  const parsed = UserConfigSchema.safeParse(JSON.parse(raw));
3417
3710
  this.config = parsed.success ? parsed.data : {};
3418
3711
  } catch {
@@ -3425,21 +3718,22 @@ var UserConfigManager = class {
3425
3718
  this.config = config;
3426
3719
  await this.atomicWrite(config);
3427
3720
  }
3428
- async getEditorPreferences(projectId) {
3721
+ async getEditorPreferences(ref) {
3429
3722
  const config = await this.getConfig();
3430
- return config[projectId]?.settings?.editors ?? {};
3723
+ return config[configKey(ref)]?.settings?.editors ?? config[ref.id]?.settings?.editors ?? {};
3431
3724
  }
3432
- async setEditorPreference(projectId, target, editorKey) {
3725
+ async setEditorPreference(ref, target, editorKey) {
3433
3726
  const config = await this.getConfig();
3434
- if (!config[projectId]) config[projectId] = {};
3435
- if (!config[projectId].settings) config[projectId].settings = {};
3436
- if (!config[projectId].settings.editors) config[projectId].settings.editors = {};
3437
- 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;
3438
3732
  await this.saveConfig(config);
3439
3733
  }
3440
- async removeEditorPreference(projectId, target) {
3734
+ async removeEditorPreference(ref, target) {
3441
3735
  const config = await this.getConfig();
3442
- const editors = config[projectId]?.settings?.editors;
3736
+ const editors = config[configKey(ref)]?.settings?.editors;
3443
3737
  if (editors && target in editors) {
3444
3738
  delete editors[target];
3445
3739
  await this.saveConfig(config);
@@ -3462,13 +3756,12 @@ async function getServiceCwd(serviceId) {
3462
3756
  if (!service) {
3463
3757
  throw new TRPCError8({ code: "NOT_FOUND", message: "Service not found" });
3464
3758
  }
3465
- const rawCwd = service.cwd?.trim() || ".";
3466
- return resolveCwd(rawCwd);
3759
+ return resolveCwd(await resolveWorkspaceCwd({ serviceId }));
3467
3760
  }
3468
3761
  async function isCommandAvailable(command) {
3469
- return new Promise((resolve6) => {
3762
+ return new Promise((resolve7) => {
3470
3763
  execFile2("which", [command], { timeout: 5e3 }, (err) => {
3471
- resolve6(!err);
3764
+ resolve7(!err);
3472
3765
  });
3473
3766
  });
3474
3767
  }
@@ -3482,35 +3775,52 @@ var editorRouter = router({
3482
3775
  );
3483
3776
  return results;
3484
3777
  }),
3485
- getPreferences: protectedProcedure.input(z11.object({ projectId: z11.string().max(128) })).query(async ({ input }) => {
3486
- 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 });
3487
3783
  }),
3488
3784
  setDefault: protectedProcedure.input(z11.object({
3489
3785
  projectId: z11.string().max(128),
3786
+ orgId: z11.string().max(128).nullable().optional(),
3490
3787
  editorKey: z11.string().max(64)
3491
3788
  })).mutation(async ({ input }) => {
3492
3789
  if (!EDITOR_BY_KEY.has(input.editorKey)) {
3493
3790
  throw new TRPCError8({ code: "BAD_REQUEST", message: `Unknown editor: ${input.editorKey}` });
3494
3791
  }
3495
- 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
+ );
3496
3797
  return { success: true };
3497
3798
  }),
3498
3799
  setServiceEditor: protectedProcedure.input(z11.object({
3499
3800
  projectId: z11.string().max(128),
3801
+ orgId: z11.string().max(128).nullable().optional(),
3500
3802
  serviceId: z11.string().max(128),
3501
3803
  editorKey: z11.string().max(64)
3502
3804
  })).mutation(async ({ input }) => {
3503
3805
  if (!EDITOR_BY_KEY.has(input.editorKey)) {
3504
3806
  throw new TRPCError8({ code: "BAD_REQUEST", message: `Unknown editor: ${input.editorKey}` });
3505
3807
  }
3506
- 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
+ );
3507
3813
  return { success: true };
3508
3814
  }),
3509
3815
  removeServiceEditor: protectedProcedure.input(z11.object({
3510
3816
  projectId: z11.string().max(128),
3817
+ orgId: z11.string().max(128).nullable().optional(),
3511
3818
  serviceId: z11.string().max(128)
3512
3819
  })).mutation(async ({ input }) => {
3513
- await userConfigManager.removeEditorPreference(input.projectId, input.serviceId);
3820
+ await userConfigManager.removeEditorPreference(
3821
+ { id: input.projectId, orgId: input.orgId },
3822
+ input.serviceId
3823
+ );
3514
3824
  return { success: true };
3515
3825
  }),
3516
3826
  open: protectedProcedure.input(z11.object({
@@ -3524,7 +3834,7 @@ var editorRouter = router({
3524
3834
  const expanded = input.path.replace(/^~(\/|$)/, `${process.env["HOME"] ?? "/tmp"}$1`).replace(/\$\{(\w+)\}|\$(\w+)/g, (_, a, b) => process.env[a || b] ?? "");
3525
3835
  const resolvedPath = resolve2(expanded || ".");
3526
3836
  try {
3527
- await stat2(resolvedPath);
3837
+ await stat3(resolvedPath);
3528
3838
  } catch (err) {
3529
3839
  if (err instanceof TRPCError8) throw err;
3530
3840
  throw new TRPCError8({ code: "BAD_REQUEST", message: `Path does not exist: ${resolvedPath}` });
@@ -3548,12 +3858,12 @@ var editorRouter = router({
3548
3858
  throw new TRPCError8({ code: "BAD_REQUEST", message: `Unknown editor: ${input.editorKey}` });
3549
3859
  }
3550
3860
  const cwd = await getServiceCwd(input.serviceId);
3551
- const filePath = join5(cwd, input.relativePath);
3861
+ const filePath = join7(cwd, input.relativePath);
3552
3862
  if (!filePath.startsWith(cwd)) {
3553
3863
  throw new TRPCError8({ code: "BAD_REQUEST", message: "Path traversal not allowed" });
3554
3864
  }
3555
3865
  try {
3556
- await stat2(filePath);
3866
+ await stat3(filePath);
3557
3867
  } catch {
3558
3868
  throw new TRPCError8({ code: "BAD_REQUEST", message: `File does not exist: ${filePath}` });
3559
3869
  }
@@ -3571,8 +3881,8 @@ var editorRouter = router({
3571
3881
  // ../server/src/trpc/routers/api-keys.ts
3572
3882
  import { z as z12 } from "zod";
3573
3883
  import { TRPCError as TRPCError9 } from "@trpc/server";
3574
- import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
3575
- 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";
3576
3886
  var ALLOWED_SKILL_PATHS = /* @__PURE__ */ new Set([
3577
3887
  ".claude/skills/runeya/SKILL.md",
3578
3888
  ".cursor/rules/runeya.mdc",
@@ -3619,9 +3929,9 @@ var apiKeysRouter = router({
3619
3929
  if (!ALLOWED_SKILL_PATHS.has(input.relativePath)) {
3620
3930
  throw new TRPCError9({ code: "BAD_REQUEST", message: "Chemin non autoris\xE9" });
3621
3931
  }
3622
- const abs = join6(process.cwd(), input.relativePath);
3623
- await mkdir7(dirname3(abs), { recursive: true });
3624
- 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 });
3625
3935
  return { path: input.relativePath };
3626
3936
  })
3627
3937
  });
@@ -3947,17 +4257,17 @@ function buildApiDocs(apiBaseUrl, apiToken) {
3947
4257
  }
3948
4258
 
3949
4259
  // ../server/src/services/claude-transcript-sync.ts
3950
- import { readdir as readdir2, access, readFile as readFile8 } from "fs/promises";
4260
+ import { readdir as readdir2, access, readFile as readFile9 } from "fs/promises";
3951
4261
  import { watch } from "fs";
3952
4262
  import { homedir as homedir3 } from "os";
3953
- import { join as join8 } from "path";
4263
+ import { join as join10 } from "path";
3954
4264
 
3955
4265
  // ../server/src/services/conversation-store.ts
3956
4266
  import { randomUUID as randomUUID3 } from "crypto";
3957
- import { readFile as readFile7, writeFile as writeFile8, rename as rename7, mkdir as mkdir8, chmod as chmod6 } from "fs/promises";
3958
- 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";
3959
4269
  import { EventEmitter as EventEmitter2 } from "events";
3960
- var FILENAME3 = "conversations.private.json";
4270
+ var FILENAME4 = "conversations.private.json";
3961
4271
  async function localProjectId() {
3962
4272
  const projects = await projectStore.list().catch(() => []);
3963
4273
  return projects.find((p) => !p.orgId)?.id ?? null;
@@ -3977,11 +4287,11 @@ var ConversationStore = class {
3977
4287
  * root, but a launch directory owning its own key keeps both together.
3978
4288
  */
3979
4289
  getFilePath() {
3980
- return keyedFilePath(FILENAME3);
4290
+ return keyedFilePath(FILENAME4);
3981
4291
  }
3982
4292
  /** Where older versions kept it — read only, so an older Runeya keeps working. */
3983
4293
  getLegacyFilePath() {
3984
- return legacyMachineFilePath(FILENAME3);
4294
+ return legacyMachineFilePath(FILENAME4);
3985
4295
  }
3986
4296
  /**
3987
4297
  * Read the machine file, falling back to the launch directory of old.
@@ -3991,13 +4301,13 @@ var ConversationStore = class {
3991
4301
  */
3992
4302
  async readFromDisk() {
3993
4303
  try {
3994
- return await readFile7(await this.getFilePath(), "utf-8");
4304
+ return await readFile8(await this.getFilePath(), "utf-8");
3995
4305
  } catch {
3996
- return readFile7(this.getLegacyFilePath(), "utf-8");
4306
+ return readFile8(this.getLegacyFilePath(), "utf-8");
3997
4307
  }
3998
4308
  }
3999
4309
  async ensureDir() {
4000
- await mkdir8(dirname4(await this.getFilePath()), { recursive: true });
4310
+ await mkdir9(dirname5(await this.getFilePath()), { recursive: true });
4001
4311
  }
4002
4312
  encryptMessages(messages) {
4003
4313
  return encryptValue(JSON.stringify(messages));
@@ -4061,16 +4371,17 @@ var ConversationStore = class {
4061
4371
  return;
4062
4372
  }
4063
4373
  this.loaded = true;
4374
+ await ensureProjectsLoaded();
4064
4375
  const vaults = await allVaultRoots();
4065
- const legacyPaths = [this.getLegacyFilePath(), await keyedFilePath(FILENAME3)];
4376
+ const legacyPaths = [this.getLegacyFilePath(), await keyedFilePath(FILENAME4)];
4066
4377
  const sources = [
4067
4378
  ...legacyPaths.map((path) => ({ path, legacy: true })),
4068
- ...vaults.map((root) => ({ path: join7(root, FILENAME3), legacy: false }))
4379
+ ...vaults.map((root) => ({ path: join9(root, FILENAME4), legacy: false }))
4069
4380
  ];
4070
4381
  for (const { path, legacy } of sources) {
4071
4382
  let data;
4072
4383
  try {
4073
- data = JSON.parse(await readFile7(path, "utf-8"));
4384
+ data = JSON.parse(await readFile8(path, "utf-8"));
4074
4385
  } catch {
4075
4386
  continue;
4076
4387
  }
@@ -4080,7 +4391,7 @@ var ConversationStore = class {
4080
4391
  const conv = this.fromDisk(item);
4081
4392
  if (!conv.projectId && inherited) conv.projectId = inherited;
4082
4393
  this.conversations.set(item.id, conv);
4083
- this.roots.set(item.id, dirname4(path));
4394
+ this.roots.set(item.id, dirname5(path));
4084
4395
  } catch {
4085
4396
  }
4086
4397
  }
@@ -4105,12 +4416,12 @@ var ConversationStore = class {
4105
4416
  byRoot.set(root, bucket);
4106
4417
  }
4107
4418
  for (const [root, data] of byRoot) {
4108
- await mkdir8(root, { recursive: true });
4109
- const filePath = join7(root, FILENAME3);
4419
+ await mkdir9(root, { recursive: true });
4420
+ const filePath = join9(root, FILENAME4);
4110
4421
  const tmpPath = `${filePath}.tmp`;
4111
- await writeFile8(tmpPath, JSON.stringify(data, null, 2), "utf-8");
4112
- await rename7(tmpPath, filePath);
4113
- 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);
4114
4425
  }
4115
4426
  }
4116
4427
  /**
@@ -4120,8 +4431,22 @@ var ConversationStore = class {
4120
4431
  * key's own directory — never lost for want of an owner.
4121
4432
  */
4122
4433
  async rootFor(conv) {
4123
- if (conv.projectId) return rootOfProject(conv.projectId);
4124
- 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 };
4125
4450
  }
4126
4451
  async save() {
4127
4452
  const write = this.saveQueue.then(() => this.saveInternal());
@@ -4137,7 +4462,7 @@ var ConversationStore = class {
4137
4462
  await this.load();
4138
4463
  return this.conversations.get(id) ?? null;
4139
4464
  }
4140
- 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) {
4141
4466
  await this.load();
4142
4467
  const now = Date.now();
4143
4468
  const conv = {
@@ -4156,6 +4481,9 @@ var ConversationStore = class {
4156
4481
  updatedAt: now
4157
4482
  };
4158
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
+ }
4159
4487
  await this.save();
4160
4488
  this.events.emit("changed", { type: "created", conversation: this.snapshotConversation(conv) });
4161
4489
  return conv;
@@ -4218,7 +4546,7 @@ var ConversationStore = class {
4218
4546
  var conversationStore = new ConversationStore();
4219
4547
 
4220
4548
  // ../server/src/services/claude-transcript-sync.ts
4221
- var PROJECTS_DIR = join8(homedir3(), ".claude", "projects");
4549
+ var PROJECTS_DIR = join10(homedir3(), ".claude", "projects");
4222
4550
  var DEFAULT_TITLES = /* @__PURE__ */ new Set(["", "New conversation", "Nouvelle conversation"]);
4223
4551
  async function findClaudeSessionFile(sessionId) {
4224
4552
  let entries;
@@ -4229,7 +4557,7 @@ async function findClaudeSessionFile(sessionId) {
4229
4557
  }
4230
4558
  const filename = `${sessionId}.jsonl`;
4231
4559
  for (const dir of entries) {
4232
- const candidate = join8(PROJECTS_DIR, dir, filename);
4560
+ const candidate = join10(PROJECTS_DIR, dir, filename);
4233
4561
  try {
4234
4562
  await access(candidate);
4235
4563
  return candidate;
@@ -4244,7 +4572,7 @@ async function claudeSessionExists(sessionId) {
4244
4572
  async function readClaudeTranscript(sessionId) {
4245
4573
  const path = await findClaudeSessionFile(sessionId);
4246
4574
  if (!path) return { found: false, path: null, entries: [] };
4247
- const raw = await readFile8(path, "utf-8");
4575
+ const raw = await readFile9(path, "utf-8");
4248
4576
  const entries = [];
4249
4577
  for (const line of raw.split("\n")) {
4250
4578
  if (!line.trim()) continue;
@@ -4392,7 +4720,7 @@ async function startClaudeTranscriptWatcher() {
4392
4720
  }
4393
4721
  for (const dir of topLevel) {
4394
4722
  try {
4395
- const w = watch(join8(PROJECTS_DIR, dir), (_event, filename) => {
4723
+ const w = watch(join10(PROJECTS_DIR, dir), (_event, filename) => {
4396
4724
  handleFsEvent(filename ? filename.toString() : null);
4397
4725
  });
4398
4726
  w.on("error", () => {
@@ -4418,8 +4746,8 @@ function stopClaudeTranscriptWatcher() {
4418
4746
  // ../server/src/services/codex-sessions.ts
4419
4747
  import { readdir as readdir3 } from "fs/promises";
4420
4748
  import { homedir as homedir4 } from "os";
4421
- import { join as join9 } from "path";
4422
- var SESSIONS_DIR = join9(homedir4(), ".codex", "sessions");
4749
+ import { join as join11 } from "path";
4750
+ var SESSIONS_DIR = join11(homedir4(), ".codex", "sessions");
4423
4751
  async function findCodexSessionFile(sessionId) {
4424
4752
  const suffix = `-${sessionId}.jsonl`;
4425
4753
  async function walk(dir) {
@@ -4430,7 +4758,7 @@ async function findCodexSessionFile(sessionId) {
4430
4758
  return null;
4431
4759
  }
4432
4760
  for (const entry of entries) {
4433
- const full = join9(dir, entry.name);
4761
+ const full = join11(dir, entry.name);
4434
4762
  if (entry.isDirectory()) {
4435
4763
  const found = await walk(full);
4436
4764
  if (found) return found;
@@ -4805,7 +5133,7 @@ function parse(text) {
4805
5133
  return result;
4806
5134
  }
4807
5135
  function getClaudeUsage() {
4808
- return new Promise((resolve6, reject) => {
5136
+ return new Promise((resolve7, reject) => {
4809
5137
  const env2 = { ...process.env };
4810
5138
  delete env2["CLAUDECODE"];
4811
5139
  let claudeBin = "claude";
@@ -4836,7 +5164,7 @@ function getClaudeUsage() {
4836
5164
  clearTimeout(hardTimeout);
4837
5165
  term?.kill();
4838
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", "");
4839
- resolve6(parse(clean));
5167
+ resolve7(parse(clean));
4840
5168
  }
4841
5169
  const hardTimeout = setTimeout(() => {
4842
5170
  if (!done) finish();
@@ -4970,7 +5298,7 @@ function parseCodexStatusText(text) {
4970
5298
  return entries;
4971
5299
  }
4972
5300
  function getCodexUsageEntries() {
4973
- return new Promise((resolve6, reject) => {
5301
+ return new Promise((resolve7, reject) => {
4974
5302
  let term = null;
4975
5303
  try {
4976
5304
  term = pty2.spawn("codex", [], {
@@ -5000,7 +5328,7 @@ function getCodexUsageEntries() {
5000
5328
  const debugClean = stripAnsi(buffer).slice(-6e3);
5001
5329
  process.stdout.write("[codex-usage] raw tail:\n" + debugClean + "\n");
5002
5330
  }
5003
- resolve6(parseCodexStatusText(buffer));
5331
+ resolve7(parseCodexStatusText(buffer));
5004
5332
  };
5005
5333
  const hardTimeout = setTimeout(() => {
5006
5334
  finish();
@@ -5390,12 +5718,12 @@ var ScenarioStore = class {
5390
5718
  var scenarioStore = new ScenarioStore();
5391
5719
 
5392
5720
  // ../server/src/services/scenario-run-store.ts
5393
- import { mkdir as mkdir10, rm as rm2, readFile as readFile10 } from "fs/promises";
5394
- 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";
5395
5723
 
5396
5724
  // ../server/src/services/run-store-utils.ts
5397
- import { chmod as chmod7, mkdir as mkdir9, rename as rename8, writeFile as writeFile9, readFile as readFile9 } from "fs/promises";
5398
- 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";
5399
5727
  var SAFE_PATH_SEGMENT_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;
5400
5728
  function resolveConversationRunDir(rootDirName, conversationId) {
5401
5729
  if (!SAFE_PATH_SEGMENT_RE.test(conversationId)) {
@@ -5409,18 +5737,18 @@ function resolveConversationRunDir(rootDirName, conversationId) {
5409
5737
  return dir;
5410
5738
  }
5411
5739
  async function atomicWriteSecure(filePath, content) {
5412
- await mkdir9(dirname5(filePath), { recursive: true });
5740
+ await mkdir10(dirname6(filePath), { recursive: true });
5413
5741
  const tmpPath = `${filePath}.tmp`;
5414
- await writeFile9(tmpPath, content, "utf-8");
5415
- await rename8(tmpPath, filePath);
5416
- await chmod7(filePath, 384);
5742
+ await writeFile10(tmpPath, content, "utf-8");
5743
+ await rename9(tmpPath, filePath);
5744
+ await chmod8(filePath, 384);
5417
5745
  }
5418
5746
  async function atomicWriteJsonSecure(filePath, data, pretty = false) {
5419
5747
  await atomicWriteSecure(filePath, JSON.stringify(data, null, pretty ? 2 : 0));
5420
5748
  }
5421
5749
  async function readJsonFileSafe(filePath) {
5422
5750
  try {
5423
- const raw = await readFile9(filePath, "utf-8");
5751
+ const raw = await readFile10(filePath, "utf-8");
5424
5752
  return JSON.parse(raw);
5425
5753
  } catch {
5426
5754
  return null;
@@ -5443,7 +5771,7 @@ var ScenarioRunStore = class _ScenarioRunStore {
5443
5771
  }
5444
5772
  async initializeConversationWorkspace(conversationId, scenario, userInput) {
5445
5773
  const dir = this.getRunDir(conversationId);
5446
- await mkdir10(dir, { recursive: true });
5774
+ await mkdir11(dir, { recursive: true });
5447
5775
  await this.saveScenarioSnapshot(conversationId, scenario);
5448
5776
  const nodesById = new Map(scenario.nodes.map((p) => [p.id, p]));
5449
5777
  const teamMap = new Map(scenario.groups.map((t2) => [t2.id, t2.name]));
@@ -5468,8 +5796,8 @@ var ScenarioRunStore = class _ScenarioRunStore {
5468
5796
  "- Use read-only references from linked upstream files as context.",
5469
5797
  ""
5470
5798
  ].join("\n");
5471
- await atomicWriteSecure(join10(dir, "README.md"), overview);
5472
- 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
5473
5801
 
5474
5802
  ${userInput.trim() || "(empty)"}
5475
5803
  `);
@@ -5495,7 +5823,7 @@ ${userInput.trim() || "(empty)"}
5495
5823
  "- Waiting for assignment.",
5496
5824
  ""
5497
5825
  ].join("\n");
5498
- await atomicWriteSecure(join10(dir, fileName), content);
5826
+ await atomicWriteSecure(join12(dir, fileName), content);
5499
5827
  }
5500
5828
  }
5501
5829
  async deleteRunDir(conversationId) {
@@ -5555,7 +5883,7 @@ ${userInput.trim() || "(empty)"}
5555
5883
  if (!runState) return;
5556
5884
  try {
5557
5885
  const dir = this.getRunDir(conversationId);
5558
- await mkdir10(dir, { recursive: true });
5886
+ await mkdir11(dir, { recursive: true });
5559
5887
  const data = {
5560
5888
  nodeStates: { ...runState.nodeStates },
5561
5889
  nodeOutputs: { ...runState.nodeOutputs },
@@ -5563,18 +5891,18 @@ ${userInput.trim() || "(empty)"}
5563
5891
  ...Object.keys(runState.nodeAgentIds).length > 0 ? { nodeAgentIds: { ...runState.nodeAgentIds } } : {},
5564
5892
  ...Object.keys(runState.waitingInputs).length > 0 ? { waitingInputs: { ...runState.waitingInputs } } : {}
5565
5893
  };
5566
- await atomicWriteJsonSecure(join10(dir, "run-state.json"), data);
5894
+ await atomicWriteJsonSecure(join12(dir, "run-state.json"), data);
5567
5895
  } catch {
5568
5896
  }
5569
5897
  }
5570
5898
  async loadPersistedRunState(conversationId) {
5571
5899
  const dir = this.getRunDir(conversationId);
5572
- return readJsonFileSafe(join10(dir, "run-state.json"));
5900
+ return readJsonFileSafe(join12(dir, "run-state.json"));
5573
5901
  }
5574
5902
  async readUserRequest(conversationId) {
5575
5903
  try {
5576
5904
  const dir = this.getRunDir(conversationId);
5577
- const content = await readFile10(join10(dir, "USER_REQUEST.md"), "utf-8");
5905
+ const content = await readFile11(join12(dir, "USER_REQUEST.md"), "utf-8");
5578
5906
  return content.replace(/^#\s*User request\s*\n+/, "").trim() || null;
5579
5907
  } catch {
5580
5908
  return null;
@@ -5583,23 +5911,23 @@ ${userInput.trim() || "(empty)"}
5583
5911
  async readReadme(conversationId) {
5584
5912
  try {
5585
5913
  const dir = this.getRunDir(conversationId);
5586
- return await readFile10(join10(dir, "README.md"), "utf-8");
5914
+ return await readFile11(join12(dir, "README.md"), "utf-8");
5587
5915
  } catch {
5588
5916
  return null;
5589
5917
  }
5590
5918
  }
5591
5919
  async saveScenarioSnapshot(conversationId, scenario) {
5592
5920
  const dir = this.getRunDir(conversationId);
5593
- await mkdir10(dir, { recursive: true });
5921
+ await mkdir11(dir, { recursive: true });
5594
5922
  await atomicWriteJsonSecure(
5595
- join10(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE),
5923
+ join12(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE),
5596
5924
  scenario,
5597
5925
  true
5598
5926
  );
5599
5927
  }
5600
5928
  async loadScenarioSnapshot(conversationId) {
5601
5929
  const dir = this.getRunDir(conversationId);
5602
- return readJsonFileSafe(join10(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE));
5930
+ return readJsonFileSafe(join12(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE));
5603
5931
  }
5604
5932
  };
5605
5933
  var scenarioRunStore = new ScenarioRunStore();
@@ -5645,9 +5973,9 @@ async function fetchProviderModels(provider, plaintextKey) {
5645
5973
 
5646
5974
  // ../server/src/services/image-storage.service.ts
5647
5975
  import { randomUUID as randomUUID4 } from "crypto";
5648
- 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";
5649
5977
  import { existsSync } from "fs";
5650
- 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";
5651
5979
  var ALLOWED_MIME_TYPES = /* @__PURE__ */ new Set([
5652
5980
  "image/png",
5653
5981
  "image/jpeg",
@@ -5664,7 +5992,7 @@ var ImageStorageService = class {
5664
5992
  this.dir = dir;
5665
5993
  }
5666
5994
  async ensureTmpDir() {
5667
- await mkdir11(this.dir, { recursive: true, mode: 448 });
5995
+ await mkdir12(this.dir, { recursive: true, mode: 448 });
5668
5996
  }
5669
5997
  async saveImage(buffer, originalName, mimeType, contextId) {
5670
5998
  if (!ALLOWED_MIME_TYPES.has(mimeType)) {
@@ -5684,15 +6012,15 @@ var ImageStorageService = class {
5684
6012
  const imageId = randomUUID4();
5685
6013
  const safeExt = ext.replace(/[^a-z.]/g, "");
5686
6014
  const fileName = `${imageId}${safeExt}`;
5687
- const filePath = join11(this.dir, fileName);
6015
+ const filePath = join13(this.dir, fileName);
5688
6016
  const resolvedPath = resolve4(filePath);
5689
6017
  const resolvedDir = resolve4(this.dir);
5690
6018
  if (resolvedPath !== resolvedDir && !resolvedPath.startsWith(resolvedDir + sep2)) {
5691
6019
  throw new Error("Invalid file path");
5692
6020
  }
5693
- await writeFile10(filePath, buffer, { mode: 384 });
6021
+ await writeFile11(filePath, buffer, { mode: 384 });
5694
6022
  const meta = { imageId, filePath, mimeType, originalName, createdAt: Date.now(), contextId };
5695
- 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 });
5696
6024
  return meta;
5697
6025
  }
5698
6026
  /**
@@ -5710,7 +6038,7 @@ var ImageStorageService = class {
5710
6038
  const files = await readdir4(this.dir);
5711
6039
  const metaFile = files.find((f) => f.startsWith(imageId) && f.endsWith(".meta.json"));
5712
6040
  if (!metaFile) return null;
5713
- 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"));
5714
6042
  if (expectedContextId !== void 0 && raw.contextId !== expectedContextId) return null;
5715
6043
  if (!existsSync(raw.filePath)) return null;
5716
6044
  return { filePath: raw.filePath, mimeType: raw.mimeType, contextId: raw.contextId };
@@ -5736,7 +6064,7 @@ var ImageStorageService = class {
5736
6064
  await Promise.all(
5737
6065
  files.filter((f) => f.endsWith(".meta.json")).map(async (file) => {
5738
6066
  try {
5739
- 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"));
5740
6068
  if (raw.contextId === contextId) {
5741
6069
  await unlink2(raw.filePath).catch(() => {
5742
6070
  });
@@ -5751,7 +6079,7 @@ var ImageStorageService = class {
5751
6079
  async readImageAsBase64(imageId, expectedContextId) {
5752
6080
  const result = await this.getImagePath(imageId, expectedContextId);
5753
6081
  if (!result) return null;
5754
- const buffer = await readFile11(result.filePath);
6082
+ const buffer = await readFile12(result.filePath);
5755
6083
  return { base64: buffer.toString("base64"), mimeType: result.mimeType };
5756
6084
  }
5757
6085
  async listExpiredImages(ttlMs = IMAGE_TTL_MS) {
@@ -5766,7 +6094,7 @@ var ImageStorageService = class {
5766
6094
  for (const file of files) {
5767
6095
  if (!file.endsWith(".meta.json")) continue;
5768
6096
  try {
5769
- 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"));
5770
6098
  if (now - raw.createdAt > ttlMs) expired.push(raw);
5771
6099
  } catch {
5772
6100
  }
@@ -5838,10 +6166,10 @@ import { basename } from "path";
5838
6166
  import { TRPCError as TRPCError10 } from "@trpc/server";
5839
6167
 
5840
6168
  // ../server/src/services/cloud-session-store.ts
5841
- import { readFile as readFile12, writeFile as writeFile11, rename as rename9, mkdir as mkdir12, chmod as chmod8, unlink as unlink3 } from "fs/promises";
5842
- 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";
5843
6171
  import { randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "crypto";
5844
- var FILENAME4 = "cloud-session.json";
6172
+ var FILENAME5 = "cloud-session.json";
5845
6173
  var STATE_TTL_MS = 5 * 60 * 1e3;
5846
6174
  var CloudSessionStore = class {
5847
6175
  session = null;
@@ -5853,11 +6181,11 @@ var CloudSessionStore = class {
5853
6181
  * root, but a launch directory owning its own key keeps both together.
5854
6182
  */
5855
6183
  getFilePath() {
5856
- return keyedFilePath(FILENAME4);
6184
+ return keyedFilePath(FILENAME5);
5857
6185
  }
5858
6186
  /** Where older versions kept it — read only, so an older Runeya keeps working. */
5859
6187
  getLegacyFilePath() {
5860
- return legacyMachineFilePath(FILENAME4);
6188
+ return legacyMachineFilePath(FILENAME5);
5861
6189
  }
5862
6190
  /**
5863
6191
  * Read the machine file, falling back to the launch directory of old.
@@ -5867,9 +6195,9 @@ var CloudSessionStore = class {
5867
6195
  */
5868
6196
  async readFromDisk() {
5869
6197
  try {
5870
- return await readFile12(await this.getFilePath(), "utf-8");
6198
+ return await readFile13(await this.getFilePath(), "utf-8");
5871
6199
  } catch {
5872
- return readFile12(this.getLegacyFilePath(), "utf-8");
6200
+ return readFile13(this.getLegacyFilePath(), "utf-8");
5873
6201
  }
5874
6202
  }
5875
6203
  async load() {
@@ -5916,12 +6244,12 @@ var CloudSessionStore = class {
5916
6244
  }
5917
6245
  }
5918
6246
  async save() {
5919
- await mkdir12(dirname6(await this.getFilePath()), { recursive: true });
6247
+ await mkdir13(dirname7(await this.getFilePath()), { recursive: true });
5920
6248
  const filePath = await this.getFilePath();
5921
6249
  const tmpPath = filePath + ".tmp";
5922
- await writeFile11(tmpPath, JSON.stringify(this.session, null, 2), "utf-8");
5923
- await rename9(tmpPath, filePath);
5924
- 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);
5925
6253
  }
5926
6254
  /** Store the session token obtained from the one-time-token exchange. */
5927
6255
  async set(cloudUrl, token) {
@@ -5939,6 +6267,18 @@ var CloudSessionStore = class {
5939
6267
  return null;
5940
6268
  }
5941
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
+ }
5942
6282
  async isLinked() {
5943
6283
  return await this.getToken() !== null;
5944
6284
  }
@@ -6014,9 +6354,10 @@ var cloudAuthRouter = router({
6014
6354
  const url = `${getCloudAppUrl()}/link-local?callback=${encodeURIComponent(callback)}&state=${encodeURIComponent(state)}`;
6015
6355
  return { url };
6016
6356
  }),
6017
- /** Whether this machine holds a cloud session. */
6357
+ /** Whether this machine holds a cloud session, and against which cloud. */
6018
6358
  status: protectedProcedure.query(async () => {
6019
- return { linked: await cloudSessionStore.isLinked() };
6359
+ const linked = await cloudSessionStore.isLinked();
6360
+ return { linked, cloudUrl: linked ? await cloudSessionStore.getCloudUrl() : null };
6020
6361
  }),
6021
6362
  /** The cloud session (user + org), fetched with the stored token. */
6022
6363
  session: protectedProcedure.query(async () => {
@@ -6566,12 +6907,12 @@ function tailTranscript(opts) {
6566
6907
  const poll = setInterval(() => {
6567
6908
  void readMore();
6568
6909
  }, 500);
6569
- await new Promise((resolve6) => {
6910
+ await new Promise((resolve7) => {
6570
6911
  const tick = setInterval(() => {
6571
6912
  if (stopped) {
6572
6913
  clearInterval(tick);
6573
6914
  clearInterval(poll);
6574
- resolve6();
6915
+ resolve7();
6575
6916
  }
6576
6917
  }, 100);
6577
6918
  });
@@ -6590,7 +6931,7 @@ function tailTranscript(opts) {
6590
6931
 
6591
6932
  // ../server/src/services/ai-runners/claude-pty-runner.ts
6592
6933
  async function getAgentManager() {
6593
- const { agentManager: agentManager2 } = await import("./agent-manager-2I47P7CA.js");
6934
+ const { agentManager: agentManager2 } = await import("./agent-manager-JJYZDLNB.js");
6594
6935
  return agentManager2;
6595
6936
  }
6596
6937
  function buildPtyArgs(params, sessionId, opts) {
@@ -6603,7 +6944,7 @@ ${apiDocs}` : systemContext;
6603
6944
  const systemPrompt = skipTokenInstructions ? fullSystemContext : `${fullSystemContext}
6604
6945
 
6605
6946
  ${TOKEN_INSTRUCTIONS}`;
6606
- const mcpArgs = mcpConfig && !opts.excludeMcp ? ["--mcp-config", mcpConfig] : [];
6947
+ const mcpArgs = mcpConfig && !opts.excludeMcp ? ["--mcp-config", mcpConfig, "--strict-mcp-config"] : [];
6607
6948
  const args = [
6608
6949
  ...sessionArg,
6609
6950
  "--dangerously-skip-permissions",
@@ -6669,7 +7010,7 @@ async function runClaudeViaPty(params) {
6669
7010
  return a;
6670
7011
  });
6671
7012
  params.onCommand?.(`claude ${displayArgs.join(" ")}`);
6672
- return new Promise((resolve6, reject) => {
7013
+ return new Promise((resolve7, reject) => {
6673
7014
  let settled = false;
6674
7015
  let spawnId = null;
6675
7016
  const state = { assistantText: "", tools: /* @__PURE__ */ new Map() };
@@ -6692,7 +7033,7 @@ async function runClaudeViaPty(params) {
6692
7033
  settled = true;
6693
7034
  cleanup();
6694
7035
  void killSpawn();
6695
- resolve6(result);
7036
+ resolve7(result);
6696
7037
  };
6697
7038
  const fail = (err) => {
6698
7039
  if (settled) return;
@@ -6897,7 +7238,7 @@ ${apiDocs}` : systemContext;
6897
7238
 
6898
7239
  ${TOKEN_INSTRUCTIONS}`;
6899
7240
  const inputArgs = hasImages ? ["--input-format", "stream-json"] : ["-p", prompt];
6900
- const mcpArgs = mcpConfig && !opts?.excludeMcp ? ["--mcp-config", mcpConfig] : [];
7241
+ const mcpArgs = mcpConfig && !opts?.excludeMcp ? ["--mcp-config", mcpConfig, "--strict-mcp-config"] : [];
6901
7242
  const args = [
6902
7243
  ...sessionArg,
6903
7244
  "--output-format",
@@ -6996,7 +7337,7 @@ var ClaudeCodeRunner = class {
6996
7337
  runLocal(params) {
6997
7338
  const { signal, skipTokenInstructions } = params;
6998
7339
  const { args, env: env2, stdinData, sessionArg, hasImages: _hasImages } = buildClaudeSpawnConfig(params);
6999
- return new Promise((resolve6, reject) => {
7340
+ return new Promise((resolve7, reject) => {
7000
7341
  const totalArgSize = args.reduce((s, a) => s + a.length + 1, 0);
7001
7342
  console.log(`[claude-code-runner] arg size: ${totalArgSize} bytes (${(totalArgSize / 1024 / 1024).toFixed(2)} MB), session: ${sessionArg.join(" ")}, images: ${params.images?.length ?? 0}`);
7002
7343
  const displayArgs = args.map((a) => {
@@ -7038,7 +7379,7 @@ var ClaudeCodeRunner = class {
7038
7379
  });
7039
7380
  proc.on("close", (code) => {
7040
7381
  if (ctx.earlyResult) {
7041
- resolve6({ ...ctx.earlyResult, actualSessionId: streamState.actualSessionId });
7382
+ resolve7({ ...ctx.earlyResult, actualSessionId: streamState.actualSessionId });
7042
7383
  return;
7043
7384
  }
7044
7385
  signal.removeEventListener("abort", onAbort);
@@ -7055,7 +7396,7 @@ var ClaudeCodeRunner = class {
7055
7396
  return;
7056
7397
  }
7057
7398
  const { status, message, branchChoice } = skipTokenInstructions ? { status: "none", message: "", branchChoice: void 0 } : parseEndToken(streamState.fullContent);
7058
- 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 });
7059
7400
  });
7060
7401
  proc.on("error", (err) => {
7061
7402
  if (ctx.settled) return;
@@ -7111,12 +7452,12 @@ var CodexAppServerClient = class extends EventEmitter3 {
7111
7452
  if (forceId !== void 0 && forceId >= this.nextId) {
7112
7453
  this.nextId = forceId + 1;
7113
7454
  }
7114
- return new Promise((resolve6, reject) => {
7455
+ return new Promise((resolve7, reject) => {
7115
7456
  const timer = setTimeout(() => {
7116
7457
  this.pending.delete(id);
7117
7458
  reject(new Error(`Request ${method} (id=${id}) timed out after ${this.requestTimeoutMs}ms`));
7118
7459
  }, this.requestTimeoutMs);
7119
- this.pending.set(id, { resolve: resolve6, reject, timer });
7460
+ this.pending.set(id, { resolve: resolve7, reject, timer });
7120
7461
  this.writeLine({ jsonrpc: "2.0", method, id, params: params ?? {} });
7121
7462
  });
7122
7463
  }
@@ -7148,8 +7489,8 @@ var CodexAppServerClient = class extends EventEmitter3 {
7148
7489
  this.rl = null;
7149
7490
  const proc = this.proc;
7150
7491
  this.proc = null;
7151
- return new Promise((resolve6) => {
7152
- proc.on("exit", () => resolve6());
7492
+ return new Promise((resolve7) => {
7493
+ proc.on("exit", () => resolve7());
7153
7494
  proc.kill("SIGTERM");
7154
7495
  const forceTimer = setTimeout(() => {
7155
7496
  proc.kill("SIGKILL");
@@ -7243,7 +7584,7 @@ async function shutdownCodexAppServer() {
7243
7584
  }
7244
7585
  }
7245
7586
  async function getAgentManager2() {
7246
- const { agentManager: agentManager2 } = await import("./agent-manager-2I47P7CA.js");
7587
+ const { agentManager: agentManager2 } = await import("./agent-manager-JJYZDLNB.js");
7247
7588
  return agentManager2;
7248
7589
  }
7249
7590
  function parseRuneyaCurlCommand(command) {
@@ -7332,7 +7673,7 @@ var CodexRunner = class {
7332
7673
  method: "thread/start",
7333
7674
  params: {
7334
7675
  model: model || void 0,
7335
- cwd: process.cwd(),
7676
+ cwd: params.cwd ?? process.cwd(),
7336
7677
  approvalPolicy: "never",
7337
7678
  sandbox: "danger-full-access"
7338
7679
  },
@@ -7358,7 +7699,7 @@ ${prompt}`;
7358
7699
  }
7359
7700
  input.push({ type: "text", text: fullPrompt });
7360
7701
  const turnRef = { id: null };
7361
- return new Promise((resolve6, reject) => {
7702
+ return new Promise((resolve7, reject) => {
7362
7703
  let settled = false;
7363
7704
  let fullContent = "";
7364
7705
  let thinkingEmitted = false;
@@ -7407,7 +7748,7 @@ ${prompt}`;
7407
7748
  params.onDelta(delta);
7408
7749
  const { status, message, branchChoice } = parseEndToken(fullContent);
7409
7750
  if (status !== "none") {
7410
- settle(() => resolve6({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
7751
+ settle(() => resolve7({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
7411
7752
  }
7412
7753
  }
7413
7754
  } else if (method === "item/reasoning/summaryTextDelta" || method === "item/reasoning/textDelta") {
@@ -7481,7 +7822,7 @@ ${prompt}`;
7481
7822
  return;
7482
7823
  }
7483
7824
  const { status, message, branchChoice } = parseEndToken(fullContent);
7484
- settle(() => resolve6({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
7825
+ settle(() => resolve7({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
7485
7826
  } else if (method === "turn/errored") {
7486
7827
  const error = p["error"];
7487
7828
  const msg = typeof error?.["message"] === "string" ? error["message"] : "Codex turn errored";
@@ -7524,7 +7865,7 @@ ${prompt}`;
7524
7865
  } else {
7525
7866
  const res = await client.sendRequest("thread/start", {
7526
7867
  model: model || void 0,
7527
- cwd: process.cwd(),
7868
+ cwd: params.cwd ?? process.cwd(),
7528
7869
  approvalPolicy: "never",
7529
7870
  sandbox: "danger-full-access"
7530
7871
  });
@@ -7556,7 +7897,7 @@ ${prompt}`;
7556
7897
  sandboxPolicy: { type: "dangerFullAccess" }
7557
7898
  });
7558
7899
  const turnId = turnRes.turn.id;
7559
- return new Promise((resolve6, reject) => {
7900
+ return new Promise((resolve7, reject) => {
7560
7901
  let settled = false;
7561
7902
  let fullContent = "";
7562
7903
  let thinkingEmitted = false;
@@ -7582,7 +7923,7 @@ ${prompt}`;
7582
7923
  params.onDelta(delta);
7583
7924
  const { status, message, branchChoice } = parseEndToken(fullContent);
7584
7925
  if (status !== "none") {
7585
- settle(() => resolve6({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
7926
+ settle(() => resolve7({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
7586
7927
  }
7587
7928
  }
7588
7929
  } else if (method === "item/reasoning/summaryTextDelta" || method === "item/reasoning/textDelta") {
@@ -7656,7 +7997,7 @@ ${prompt}`;
7656
7997
  return;
7657
7998
  }
7658
7999
  const { status, message, branchChoice } = parseEndToken(fullContent);
7659
- settle(() => resolve6({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
8000
+ settle(() => resolve7({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
7660
8001
  } else if (method === "turn/errored") {
7661
8002
  const error = p["error"];
7662
8003
  const msg = typeof error?.["message"] === "string" ? error["message"] : "Codex turn errored";
@@ -7982,6 +8323,8 @@ async function startClaudeCodeSession(convId, prompt, opts = {}) {
7982
8323
  const injectedEnv = await resolveInjectedEnv(opts.environmentId);
7983
8324
  const done = new ClaudeCodeRunner().run({
7984
8325
  agentId: resolvedAgentId ?? void 0,
8326
+ // The conversation's project, not the server's launch directory.
8327
+ cwd: await conversationCwd(conv),
7985
8328
  sessionId: conv.id,
7986
8329
  prompt,
7987
8330
  isResume,
@@ -8044,6 +8387,7 @@ async function startCodexSession(convId, prompt, opts = {}) {
8044
8387
  const injectedEnv = await resolveInjectedEnv(opts.environmentId);
8045
8388
  const done = new CodexRunner().run({
8046
8389
  agentId: resolvedAgentId ?? void 0,
8390
+ cwd: await conversationCwd(conv),
8047
8391
  sessionId: conv.codexThreadId ?? "",
8048
8392
  prompt,
8049
8393
  isResume,
@@ -8094,19 +8438,24 @@ var StoredMessageSchema = z15.object({
8094
8438
  content: z15.string().max(1e5),
8095
8439
  parts: z15.array(MessagePartSchema).max(200).optional()
8096
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
+ }
8097
8446
  var chatRouter = router({
8098
8447
  conversations: router({
8099
8448
  onChanged: protectedProcedure.subscription(async function* () {
8100
8449
  const initial = await conversationStore.list();
8101
8450
  yield { type: "initial", conversations: initial };
8102
8451
  const queue = [];
8103
- let resolve6 = null;
8452
+ let resolve7 = null;
8104
8453
  const handler = (event) => {
8105
8454
  if (queue.length >= 500) queue.shift();
8106
8455
  queue.push(event);
8107
- if (resolve6) {
8108
- resolve6();
8109
- resolve6 = null;
8456
+ if (resolve7) {
8457
+ resolve7();
8458
+ resolve7 = null;
8110
8459
  }
8111
8460
  };
8112
8461
  conversationStore.onChanged(handler);
@@ -8116,7 +8465,7 @@ var chatRouter = router({
8116
8465
  yield queue.shift();
8117
8466
  } else {
8118
8467
  await new Promise((r) => {
8119
- resolve6 = r;
8468
+ resolve7 = r;
8120
8469
  });
8121
8470
  }
8122
8471
  }
@@ -8154,7 +8503,8 @@ var chatRouter = router({
8154
8503
  kanbanCardId: Id128Schema.optional(),
8155
8504
  // The project the conversation is about: only the caller knows which
8156
8505
  // one it is looking at.
8157
- projectId: Id128Schema.optional()
8506
+ projectId: Id128Schema.optional(),
8507
+ orgId: z15.string().max(128).nullable().optional()
8158
8508
  })).mutation(async ({ input }) => {
8159
8509
  let scenario = null;
8160
8510
  if (input.scenarioId) {
@@ -8170,7 +8520,8 @@ var chatRouter = router({
8170
8520
  input.kanbanBoardId,
8171
8521
  input.kanbanCardId,
8172
8522
  input.providerId,
8173
- input.projectId
8523
+ input.projectId,
8524
+ input.orgId
8174
8525
  );
8175
8526
  if (scenario) {
8176
8527
  await scenarioRunStore.initializeConversationWorkspace(
@@ -8397,6 +8748,7 @@ ${imagePaths.map((p) => `Image: ${p}`).join("\n")}` : cliPrompt;
8397
8748
  const injectedEnv = await resolveInjectedEnv(input.environmentId);
8398
8749
  new ClaudeCodeRunner().run({
8399
8750
  agentId: resolvedAgentId ?? void 0,
8751
+ cwd: await conversationCwd(conv),
8400
8752
  sessionId: conv.id,
8401
8753
  prompt: promptWithImages,
8402
8754
  isResume,
@@ -8497,6 +8849,7 @@ ${input.prompt}` : input.prompt;
8497
8849
  const injectedEnv = await resolveInjectedEnv(input.environmentId);
8498
8850
  new CodexRunner().run({
8499
8851
  agentId: resolvedAgentId ?? void 0,
8852
+ cwd: await conversationCwd(conv),
8500
8853
  sessionId: conv.codexThreadId ?? "",
8501
8854
  prompt: cliPrompt,
8502
8855
  isResume,
@@ -8664,6 +9017,10 @@ ${input.prompt}` : input.prompt;
8664
9017
  resume: z15.boolean().default(false),
8665
9018
  model: z15.string().max(128).optional(),
8666
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(),
8667
9024
  cols: z15.number().int().min(1).max(1e3).default(120),
8668
9025
  rows: z15.number().int().min(1).max(1e3).default(30),
8669
9026
  enableMcp: z15.boolean().default(false),
@@ -8674,6 +9031,10 @@ ${input.prompt}` : input.prompt;
8674
9031
  text: z15.string().max(1e6)
8675
9032
  })).max(20).optional()
8676
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
+ };
8677
9038
  const agentId = input.agentId ?? agentManager.getDefaultAgentId();
8678
9039
  if (!agentId) throw new TRPCError11({ code: "PRECONDITION_FAILED", message: "No agent available" });
8679
9040
  if (input.sessionId) {
@@ -8690,11 +9051,13 @@ ${input.prompt}` : input.prompt;
8690
9051
  rows: input.rows
8691
9052
  }).catch(() => {
8692
9053
  });
9054
+ lap("reattached to existing pty");
8693
9055
  return { spawnId: existing.spawnId, agentId, reattached: true };
8694
9056
  }
8695
9057
  } catch {
8696
9058
  }
8697
9059
  }
9060
+ lap("findPtyBySession (no reattach)");
8698
9061
  const isCodex = input.runner === "codex";
8699
9062
  const cli = isCodex ? "codex" : "claude";
8700
9063
  const args = [];
@@ -8734,8 +9097,10 @@ ${apiDocs}` : systemContext;
8734
9097
  } catch (err) {
8735
9098
  console.warn("[chat.pty.start] buildCliSystemContext failed:", err.message);
8736
9099
  }
9100
+ lap("buildCliSystemContext");
8737
9101
  }
8738
9102
  const injectedEnv = await resolveInjectedEnv(input.environmentId);
9103
+ lap("resolveInjectedEnv");
8739
9104
  const res = await agentManager.agentMutation(
8740
9105
  agentId,
8741
9106
  "ai.spawnPty",
@@ -8743,7 +9108,13 @@ ${apiDocs}` : systemContext;
8743
9108
  cli,
8744
9109
  args,
8745
9110
  env: injectedEnv,
8746
- 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
+ }),
8747
9118
  cols: input.cols,
8748
9119
  rows: input.rows,
8749
9120
  sessionId: input.sessionId,
@@ -8752,6 +9123,7 @@ ${apiDocs}` : systemContext;
8752
9123
  mcpApiToken: jwt
8753
9124
  }
8754
9125
  );
9126
+ lap("agent ai.spawnPty");
8755
9127
  const contexts = input.attachedContexts ?? [];
8756
9128
  if (contexts.length > 0) {
8757
9129
  const body = contexts.map((c) => `### ${c.label}
@@ -8818,25 +9190,31 @@ Context:
8818
9190
  })).subscription(async function* ({ input, signal }) {
8819
9191
  const agentId = input.agentId ?? agentManager.getDefaultAgentId();
8820
9192
  if (!agentId) throw new TRPCError11({ code: "PRECONDITION_FAILED", message: "No agent available" });
9193
+ const tSub = Date.now();
9194
+ let firstChunk = true;
8821
9195
  const queue = [];
8822
- let resolve6 = null;
9196
+ let resolve7 = null;
8823
9197
  let stopped = false;
8824
9198
  const unsub = agentManager.agentSubscribe(
8825
9199
  agentId,
8826
9200
  "ai.stream",
8827
9201
  { spawnId: input.spawnId, fromOffset: input.fromOffset },
8828
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
+ }
8829
9207
  queue.push(data);
8830
- if (resolve6) {
8831
- resolve6();
8832
- resolve6 = null;
9208
+ if (resolve7) {
9209
+ resolve7();
9210
+ resolve7 = null;
8833
9211
  }
8834
9212
  },
8835
9213
  () => {
8836
9214
  stopped = true;
8837
- if (resolve6) {
8838
- resolve6();
8839
- resolve6 = null;
9215
+ if (resolve7) {
9216
+ resolve7();
9217
+ resolve7 = null;
8840
9218
  }
8841
9219
  }
8842
9220
  );
@@ -8852,7 +9230,7 @@ Context:
8852
9230
  }
8853
9231
  if (stopped) break;
8854
9232
  await new Promise((r) => {
8855
- resolve6 = r;
9233
+ resolve7 = r;
8856
9234
  });
8857
9235
  }
8858
9236
  } finally {
@@ -9003,7 +9381,9 @@ Context:
9003
9381
  {
9004
9382
  cli: "node",
9005
9383
  args: [scriptPath],
9006
- 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({}),
9007
9387
  cols: input.cols,
9008
9388
  rows: input.rows,
9009
9389
  sessionId: stressSessionId,
@@ -9050,11 +9430,15 @@ async function resolveServiceContext(serviceId) {
9050
9430
  if (!agentId) {
9051
9431
  throw new TRPCError12({ code: "BAD_REQUEST", message: "No agent configured for this service" });
9052
9432
  }
9053
- return { agentId, cwd: service.cwd || "." };
9433
+ return { agentId, cwd: await resolveWorkspaceCwd({ serviceId }) };
9054
9434
  }
9055
9435
  var gitTargetShape = {
9056
9436
  serviceId: SERVICE_ID_SCHEMA.optional(),
9057
- 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()
9058
9442
  };
9059
9443
  async function resolveGitContext(input) {
9060
9444
  if (input.serviceId) return resolveServiceContext(input.serviceId);
@@ -9062,7 +9446,8 @@ async function resolveGitContext(input) {
9062
9446
  if (!agentId) {
9063
9447
  throw new TRPCError12({ code: "BAD_REQUEST", message: "No agent available" });
9064
9448
  }
9065
- return { agentId, cwd: "." };
9449
+ const cwd = await resolveAgentCwd({ projectId: input.projectId, orgId: input.orgId }) ?? ".";
9450
+ return { agentId, cwd };
9066
9451
  }
9067
9452
  var gitRouter = router({
9068
9453
  /** Get full git status */
@@ -9223,7 +9608,7 @@ Rules:
9223
9608
  onStatusChange: protectedProcedure.input(z16.object({ ...gitTargetShape })).subscription(async function* ({ input }) {
9224
9609
  const { agentId, cwd } = await resolveGitContext(input);
9225
9610
  const queue = [];
9226
- let resolve6 = null;
9611
+ let resolve7 = null;
9227
9612
  let done = false;
9228
9613
  let unsubscribe;
9229
9614
  try {
@@ -9233,16 +9618,16 @@ Rules:
9233
9618
  { cwd },
9234
9619
  (data) => {
9235
9620
  queue.push(data);
9236
- if (resolve6) {
9237
- resolve6();
9238
- resolve6 = null;
9621
+ if (resolve7) {
9622
+ resolve7();
9623
+ resolve7 = null;
9239
9624
  }
9240
9625
  },
9241
9626
  () => {
9242
9627
  done = true;
9243
- if (resolve6) {
9244
- resolve6();
9245
- resolve6 = null;
9628
+ if (resolve7) {
9629
+ resolve7();
9630
+ resolve7 = null;
9246
9631
  }
9247
9632
  }
9248
9633
  );
@@ -9258,7 +9643,7 @@ Rules:
9258
9643
  yield queue.shift();
9259
9644
  } else {
9260
9645
  await new Promise((r) => {
9261
- resolve6 = r;
9646
+ resolve7 = r;
9262
9647
  });
9263
9648
  }
9264
9649
  }
@@ -9390,9 +9775,9 @@ async function* streamWithActiveProvider(opts) {
9390
9775
  }
9391
9776
 
9392
9777
  // ../server/src/services/scenario-runner.ts
9393
- import { mkdir as mkdir13 } from "fs/promises";
9778
+ import { mkdir as mkdir14 } from "fs/promises";
9394
9779
  import { randomUUID as randomUUID7 } from "crypto";
9395
- import { join as join12 } from "path";
9780
+ import { join as join14 } from "path";
9396
9781
 
9397
9782
  // ../server/src/services/scenario-presets.ts
9398
9783
  var BUILT_IN_PRESETS = [
@@ -9606,21 +9991,21 @@ async function* tapBus(conversationId, signal) {
9606
9991
  }
9607
9992
  if (bus.done) return;
9608
9993
  if (signal.aborted) return;
9609
- await new Promise((resolve6) => {
9994
+ await new Promise((resolve7) => {
9610
9995
  const onAbort = () => {
9611
9996
  bus.listeners.delete(notify);
9612
- resolve6();
9997
+ resolve7();
9613
9998
  };
9614
9999
  const notify = () => {
9615
10000
  signal.removeEventListener("abort", onAbort);
9616
- resolve6();
10001
+ resolve7();
9617
10002
  };
9618
10003
  bus.listeners.add(notify);
9619
10004
  signal.addEventListener("abort", onAbort, { once: true });
9620
10005
  if (cursor < bus.baseIndex + bus.buffer.length || bus.done || signal.aborted) {
9621
10006
  bus.listeners.delete(notify);
9622
10007
  signal.removeEventListener("abort", onAbort);
9623
- resolve6();
10008
+ resolve7();
9624
10009
  }
9625
10010
  });
9626
10011
  }
@@ -10088,6 +10473,10 @@ async function runNodeCall(conversationId, node, systemContext, userMessage, run
10088
10473
  const turnStart = Date.now();
10089
10474
  const result = await state.runner.run({
10090
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 }),
10091
10480
  sessionId: state.sessionId,
10092
10481
  prompt: currentPrompt,
10093
10482
  isResume: state.isResume,
@@ -10124,9 +10513,9 @@ async function runNodeCall(conversationId, node, systemContext, userMessage, run
10124
10513
  publish2({ type: "node_waiting_input", nodeId: node.id, question: result.tokenMessage });
10125
10514
  await readme?.(`- \u2753 [${formatTs()}] **${node.label}** \u2014 waiting for user input
10126
10515
  `);
10127
- const answer = await new Promise((resolve6, reject) => {
10516
+ const answer = await new Promise((resolve7, reject) => {
10128
10517
  const key = pendingNodeKey(conversationId, node.id);
10129
- pendingNodeInputs.set(key, resolve6);
10518
+ pendingNodeInputs.set(key, resolve7);
10130
10519
  signal.addEventListener("abort", () => {
10131
10520
  pendingNodeInputs.delete(key);
10132
10521
  reject(new Error("Aborted"));
@@ -10166,7 +10555,7 @@ async function appendNodeOutput(conversationId, node, output) {
10166
10555
  if (!dir) return;
10167
10556
  const slug = node.label.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 60) || "member";
10168
10557
  const safeId = node.id.replace(/[^a-z0-9_-]/gi, "-").slice(0, 80);
10169
- const filePath = join12(dir, `${slug}_${safeId}.md`);
10558
+ const filePath = join14(dir, `${slug}_${safeId}.md`);
10170
10559
  const content = `## Output
10171
10560
 
10172
10561
  ${output}
@@ -10179,9 +10568,9 @@ async function appendToReadme(conversationId, bus, text) {
10179
10568
  const line = `${text}
10180
10569
  `;
10181
10570
  try {
10182
- await mkdir13(dir, { recursive: true });
10571
+ await mkdir14(dir, { recursive: true });
10183
10572
  const { appendFile: appendFile2 } = await import("fs/promises");
10184
- await appendFile2(join12(dir, "README.md"), line, "utf-8");
10573
+ await appendFile2(join14(dir, "README.md"), line, "utf-8");
10185
10574
  } catch {
10186
10575
  }
10187
10576
  publishToBus(bus, { type: "readme_append", text: line });
@@ -10201,7 +10590,7 @@ async function writeFinalMd(conversationId, bus, userRequest, content) {
10201
10590
 
10202
10591
  ${content}
10203
10592
  `;
10204
- const filePath = join12(dir, "FINAL.md");
10593
+ const filePath = join14(dir, "FINAL.md");
10205
10594
  await atomicWriteSecure(filePath, fullContent);
10206
10595
  await appendToReadme(
10207
10596
  conversationId,
@@ -10531,9 +10920,9 @@ ${outputs.get(node.id) ?? ""}`.trim();
10531
10920
  publish2({ type: "node_waiting_input", nodeId: node.id, question });
10532
10921
  await readme(`- \u2753 [${ts()}] **${node.label || node.id}** \u2014 waiting for user input
10533
10922
  `);
10534
- const answer = await new Promise((resolve6, reject) => {
10923
+ const answer = await new Promise((resolve7, reject) => {
10535
10924
  const key = pendingNodeKey(conversationId, node.id);
10536
- pendingNodeInputs.set(key, resolve6);
10925
+ pendingNodeInputs.set(key, resolve7);
10537
10926
  signal.addEventListener("abort", () => {
10538
10927
  pendingNodeInputs.delete(key);
10539
10928
  reject(new Error("Aborted"));
@@ -10932,6 +11321,10 @@ var scenarioRouter = router({
10932
11321
  runnerOverride: z17.enum(["claude-code", "codex"]).optional(),
10933
11322
  modelOverride: Id128Schema.optional(),
10934
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(),
10935
11328
  resume: z17.boolean().optional()
10936
11329
  })).subscription(async function* ({ input, signal }) {
10937
11330
  const scenario = await scenarioStore.get(input.scenarioId);
@@ -10945,7 +11338,9 @@ var scenarioRouter = router({
10945
11338
  void runScenario(input.conversationId, input.scenarioId, userRequest, {
10946
11339
  runnerOverride: input.runnerOverride,
10947
11340
  modelOverride: input.modelOverride,
10948
- agentId: input.agentId
11341
+ agentId: input.agentId,
11342
+ projectId: input.projectId,
11343
+ orgId: input.orgId
10949
11344
  }, !!input.resume);
10950
11345
  await new Promise((r) => setTimeout(r, 10));
10951
11346
  }
@@ -11066,21 +11461,21 @@ async function* tapJob(job, signal) {
11066
11461
  yield evt;
11067
11462
  }
11068
11463
  if (job.done || signal.aborted) return;
11069
- await new Promise((resolve6) => {
11464
+ await new Promise((resolve7) => {
11070
11465
  const onAbort = () => {
11071
11466
  job.listeners.delete(notify);
11072
- resolve6();
11467
+ resolve7();
11073
11468
  };
11074
11469
  const notify = () => {
11075
11470
  signal.removeEventListener("abort", onAbort);
11076
- resolve6();
11471
+ resolve7();
11077
11472
  };
11078
11473
  job.listeners.add(notify);
11079
11474
  signal.addEventListener("abort", onAbort, { once: true });
11080
11475
  if (cursor < job.baseIndex + job.buffer.length || job.done || signal.aborted) {
11081
11476
  job.listeners.delete(notify);
11082
11477
  signal.removeEventListener("abort", onAbort);
11083
- resolve6();
11478
+ resolve7();
11084
11479
  }
11085
11480
  });
11086
11481
  }
@@ -11352,13 +11747,13 @@ var kanbanQueueRouter = router({
11352
11747
  }),
11353
11748
  onChanged: protectedProcedure.subscription(async function* () {
11354
11749
  const queue = [];
11355
- let resolve6 = null;
11750
+ let resolve7 = null;
11356
11751
  const handler = () => {
11357
11752
  if (queue.length >= 50) queue.shift();
11358
11753
  queue.push(kanbanQueue.getState());
11359
- if (resolve6) {
11360
- resolve6();
11361
- resolve6 = null;
11754
+ if (resolve7) {
11755
+ resolve7();
11756
+ resolve7 = null;
11362
11757
  }
11363
11758
  };
11364
11759
  kanbanQueue.on("changed", handler);
@@ -11369,7 +11764,7 @@ var kanbanQueueRouter = router({
11369
11764
  yield queue.shift();
11370
11765
  } else {
11371
11766
  await new Promise((r) => {
11372
- resolve6 = r;
11767
+ resolve7 = r;
11373
11768
  });
11374
11769
  }
11375
11770
  }
@@ -11380,10 +11775,10 @@ var kanbanQueueRouter = router({
11380
11775
  });
11381
11776
 
11382
11777
  // ../server/src/trpc/routers/kanban.ts
11383
- 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";
11384
11779
  import { createHash } from "crypto";
11385
11780
  import { tmpdir } from "os";
11386
- import { join as join13 } from "path";
11781
+ import { join as join15 } from "path";
11387
11782
  import { z as z20 } from "zod";
11388
11783
  import { TRPCError as TRPCError17 } from "@trpc/server";
11389
11784
  function summarizeCard(card, boardId, columnTitle) {
@@ -11434,30 +11829,30 @@ function safeDecode(value) {
11434
11829
  }
11435
11830
  var MAX_ATTACHMENT_BYTES = 55 * 1024 * 1024;
11436
11831
  function attachmentCacheDir() {
11437
- return join13(tmpdir(), "runeya-kanban-attachments");
11832
+ return join15(tmpdir(), "runeya-kanban-attachments");
11438
11833
  }
11439
11834
  var ATTACHMENT_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
11440
11835
  async function purgeStaleAttachments(dir, ttlMs = ATTACHMENT_CACHE_TTL_MS) {
11441
11836
  try {
11442
11837
  const cutoff = Date.now() - ttlMs;
11443
11838
  for (const name of await readdir5(dir)) {
11444
- const path = join13(dir, name);
11445
- const info = await stat3(path).catch(() => null);
11839
+ const path = join15(dir, name);
11840
+ const info = await stat4(path).catch(() => null);
11446
11841
  if (info && info.mtimeMs < cutoff) await rm3(path, { force: true });
11447
11842
  }
11448
11843
  } catch {
11449
11844
  }
11450
11845
  }
11451
11846
  function attachmentCacheDirs() {
11452
- return [attachmentCacheDir(), join13(machineRoot(), "kanban-attachments")];
11847
+ return [attachmentCacheDir(), join15(machineRoot(), "kanban-attachments")];
11453
11848
  }
11454
11849
  async function storeAttachment(id, name, body, dirs = attachmentCacheDirs()) {
11455
11850
  const refusals = [];
11456
11851
  for (const dir of dirs) {
11457
11852
  try {
11458
- await mkdir14(dir, { recursive: true });
11459
- const path = join13(dir, safeLocalName(id, name));
11460
- await writeFile12(path, body);
11853
+ await mkdir15(dir, { recursive: true });
11854
+ const path = join15(dir, safeLocalName(id, name));
11855
+ await writeFile13(path, body);
11461
11856
  void purgeStaleAttachments(dir);
11462
11857
  return path;
11463
11858
  } catch (err) {
@@ -11644,13 +12039,28 @@ var terminalRouter = router({
11644
12039
  create: protectedProcedure.input(z21.object({
11645
12040
  agentId: z21.string().max(128),
11646
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(),
11647
12047
  cols: z21.number().int().min(1).max(512).optional(),
11648
12048
  rows: z21.number().int().min(1).max(256).optional()
11649
12049
  })).mutation(async ({ input }) => {
11650
12050
  const result = await agentManager.agentMutation(
11651
12051
  input.agentId,
11652
12052
  "terminal.create",
11653
- { 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
+ }
11654
12064
  );
11655
12065
  return result;
11656
12066
  }),
@@ -11699,7 +12109,7 @@ var terminalRouter = router({
11699
12109
  sessionId: z21.string().max(128)
11700
12110
  })).subscription(async function* ({ input }) {
11701
12111
  const queue = [];
11702
- let resolve6 = null;
12112
+ let resolve7 = null;
11703
12113
  let done = false;
11704
12114
  let unsubscribe;
11705
12115
  try {
@@ -11709,16 +12119,16 @@ var terminalRouter = router({
11709
12119
  { sessionId: input.sessionId },
11710
12120
  (data) => {
11711
12121
  if (typeof data === "string") queue.push(data);
11712
- if (resolve6) {
11713
- resolve6();
11714
- resolve6 = null;
12122
+ if (resolve7) {
12123
+ resolve7();
12124
+ resolve7 = null;
11715
12125
  }
11716
12126
  },
11717
12127
  () => {
11718
12128
  done = true;
11719
- if (resolve6) {
11720
- resolve6();
11721
- resolve6 = null;
12129
+ if (resolve7) {
12130
+ resolve7();
12131
+ resolve7 = null;
11722
12132
  }
11723
12133
  }
11724
12134
  );
@@ -11734,7 +12144,7 @@ var terminalRouter = router({
11734
12144
  yield queue.shift();
11735
12145
  } else {
11736
12146
  await new Promise((r) => {
11737
- resolve6 = r;
12147
+ resolve7 = r;
11738
12148
  });
11739
12149
  }
11740
12150
  }
@@ -11751,7 +12161,7 @@ var utilityRouter = router({
11751
12161
  wait: protectedProcedure.input(z22.object({
11752
12162
  ms: z22.number().int().min(0).max(MAX_WAIT_MS)
11753
12163
  })).mutation(async ({ input }) => {
11754
- await new Promise((resolve6) => setTimeout(resolve6, input.ms));
12164
+ await new Promise((resolve7) => setTimeout(resolve7, input.ms));
11755
12165
  return { waited: input.ms };
11756
12166
  })
11757
12167
  });
@@ -11759,8 +12169,8 @@ var utilityRouter = router({
11759
12169
  // ../server/src/trpc/routers/wiki.ts
11760
12170
  import { z as z23 } from "zod";
11761
12171
  import { TRPCError as TRPCError19 } from "@trpc/server";
11762
- 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";
11763
- 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";
11764
12174
  var SCOPE_SCHEMA = z23.union([
11765
12175
  z23.literal("global"),
11766
12176
  z23.string().regex(/^service:[A-Za-z0-9_-]{1,128}$/, "Invalid service scope")
@@ -11797,13 +12207,13 @@ async function resolveScope(scope) {
11797
12207
  throw new TRPCError19({ code: "BAD_REQUEST", message: "Sous-chemin invalide" });
11798
12208
  }
11799
12209
  const dataDirAbs = resolvePath(env.DATA_DIR);
11800
- await mkdir15(dataDirAbs, { recursive: true });
11801
- const base = dirname7(await realpath(dataDirAbs));
12210
+ await mkdir16(dataDirAbs, { recursive: true });
12211
+ const base = dirname8(await realpath(dataDirAbs));
11802
12212
  const target = resolvePath(base, sub);
11803
12213
  if (target !== base && !(target + sep3).startsWith(base + sep3)) {
11804
12214
  throw new TRPCError19({ code: "FORBIDDEN", message: "Hors du dossier de donn\xE9es du serveur" });
11805
12215
  }
11806
- await mkdir15(target, { recursive: true });
12216
+ await mkdir16(target, { recursive: true });
11807
12217
  const root = await realpath(target);
11808
12218
  if (root !== base && !(root + sep3).startsWith(base + sep3)) {
11809
12219
  throw new TRPCError19({ code: "FORBIDDEN", message: "Le dossier wiki r\xE9sout hors du dossier de donn\xE9es" });
@@ -11815,7 +12225,7 @@ async function resolveScope(scope) {
11815
12225
  if (!service) throw new TRPCError19({ code: "NOT_FOUND", message: "Service introuvable" });
11816
12226
  const agentId = await resolveEffectiveAgentId(service);
11817
12227
  if (!agentId) throw new TRPCError19({ code: "BAD_REQUEST", message: "Aucun agent configur\xE9 pour ce service" });
11818
- const cwd = (service.docsPath?.trim() || service.cwd || ".").toString();
12228
+ const cwd = await resolveWorkspaceCwd({ serviceId, override: service.docsPath?.trim() || void 0 });
11819
12229
  return { mode: "agent", agentId, cwd };
11820
12230
  }
11821
12231
  async function walkLocal(root, dir, depth, counter) {
@@ -11830,7 +12240,7 @@ async function walkLocal(root, dir, depth, counter) {
11830
12240
  for (const e of entries) {
11831
12241
  if (counter.n >= MAX_FILES) break;
11832
12242
  if (e.name.startsWith(".") && e.name !== ".") continue;
11833
- const abs = join14(dir, e.name);
12243
+ const abs = join16(dir, e.name);
11834
12244
  const rel = relative(root, abs).split(sep3).join("/");
11835
12245
  if (e.isDirectory()) {
11836
12246
  if (IGNORED_DIRS.has(e.name)) continue;
@@ -11864,7 +12274,7 @@ async function resolveSafeLocal(root, relPath, requireExist) {
11864
12274
  }
11865
12275
  return real;
11866
12276
  }
11867
- const parent = dirname7(target);
12277
+ const parent = dirname8(target);
11868
12278
  let realParent;
11869
12279
  try {
11870
12280
  realParent = await realpath(parent);
@@ -11895,10 +12305,10 @@ var wikiRouter = router({
11895
12305
  return agentManager.agentQuery(target.agentId, "wiki.readRepoMarkdown", { cwd: target.cwd, path: input.path });
11896
12306
  }
11897
12307
  const file = await resolveSafeLocal(target.root, input.path, true);
11898
- const s = await stat4(file);
12308
+ const s = await stat5(file);
11899
12309
  if (!s.isFile()) throw new TRPCError19({ code: "BAD_REQUEST", message: "Pas un fichier" });
11900
12310
  if (s.size > MAX_FILE_BYTES) throw new TRPCError19({ code: "PAYLOAD_TOO_LARGE", message: "Fichier trop volumineux" });
11901
- const content = await readFile13(file, "utf-8");
12311
+ const content = await readFile14(file, "utf-8");
11902
12312
  return { path: input.path, content };
11903
12313
  }),
11904
12314
  write: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA, content: z23.string().max(5e6) })).mutation(async ({ input }) => {
@@ -11910,8 +12320,8 @@ var wikiRouter = router({
11910
12320
  return agentManager.agentMutation(target.agentId, "wiki.writeMarkdown", { cwd: target.cwd, path: input.path, content: input.content });
11911
12321
  }
11912
12322
  const file = await resolveSafeLocal(target.root, input.path, false);
11913
- await mkdir15(dirname7(file), { recursive: true });
11914
- await writeFile13(file, input.content, "utf-8");
12323
+ await mkdir16(dirname8(file), { recursive: true });
12324
+ await writeFile14(file, input.content, "utf-8");
11915
12325
  return { path: input.path, ok: true };
11916
12326
  }),
11917
12327
  createFile: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
@@ -11924,13 +12334,13 @@ var wikiRouter = router({
11924
12334
  }
11925
12335
  const file = await resolveSafeLocal(target.root, input.path, false);
11926
12336
  try {
11927
- await stat4(file);
12337
+ await stat5(file);
11928
12338
  throw new TRPCError19({ code: "CONFLICT", message: "Fichier d\xE9j\xE0 existant" });
11929
12339
  } catch (err) {
11930
12340
  if (err instanceof TRPCError19) throw err;
11931
12341
  }
11932
- await mkdir15(dirname7(file), { recursive: true });
11933
- await writeFile13(file, "", "utf-8");
12342
+ await mkdir16(dirname8(file), { recursive: true });
12343
+ await writeFile14(file, "", "utf-8");
11934
12344
  return { path: input.path, ok: true };
11935
12345
  }),
11936
12346
  createDir: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
@@ -11939,7 +12349,7 @@ var wikiRouter = router({
11939
12349
  return agentManager.agentMutation(target.agentId, "wiki.createDir", { cwd: target.cwd, path: input.path });
11940
12350
  }
11941
12351
  const dir = await resolveSafeLocal(target.root, input.path, false);
11942
- await mkdir15(dir, { recursive: true });
12352
+ await mkdir16(dir, { recursive: true });
11943
12353
  return { path: input.path, ok: true };
11944
12354
  }),
11945
12355
  rename: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, fromPath: REL_PATH_SCHEMA, toPath: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
@@ -11950,13 +12360,13 @@ var wikiRouter = router({
11950
12360
  const from = await resolveSafeLocal(target.root, input.fromPath, true);
11951
12361
  const to = await resolveSafeLocal(target.root, input.toPath, false);
11952
12362
  try {
11953
- await stat4(to);
12363
+ await stat5(to);
11954
12364
  throw new TRPCError19({ code: "CONFLICT", message: "La cible existe d\xE9j\xE0" });
11955
12365
  } catch (err) {
11956
12366
  if (err instanceof TRPCError19) throw err;
11957
12367
  }
11958
- await mkdir15(dirname7(to), { recursive: true });
11959
- await rename10(from, to);
12368
+ await mkdir16(dirname8(to), { recursive: true });
12369
+ await rename11(from, to);
11960
12370
  return { fromPath: input.fromPath, toPath: input.toPath, ok: true };
11961
12371
  }),
11962
12372
  ensureRoot: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA })).mutation(async ({ input }) => {
@@ -11969,7 +12379,7 @@ var wikiRouter = router({
11969
12379
  if (!service) throw new TRPCError19({ code: "NOT_FOUND", message: "Service introuvable" });
11970
12380
  const agentId = await resolveEffectiveAgentId(service);
11971
12381
  if (!agentId) throw new TRPCError19({ code: "BAD_REQUEST", message: "Aucun agent configur\xE9" });
11972
- const cwd = (service.docsPath?.trim() || service.cwd || ".").toString();
12382
+ const cwd = await resolveWorkspaceCwd({ serviceId, override: service.docsPath?.trim() || void 0 });
11973
12383
  return agentManager.agentMutation(agentId, "wiki.ensureRoot", { cwd });
11974
12384
  }),
11975
12385
  delete: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
@@ -11997,18 +12407,18 @@ import { z as z24 } from "zod";
11997
12407
  import { TRPCError as TRPCError20 } from "@trpc/server";
11998
12408
 
11999
12409
  // ../server/src/services/cloud-sync-store.ts
12000
- import { readFile as readFile14, writeFile as writeFile14, rename as rename11, mkdir as mkdir16, chmod as chmod9 } from "fs/promises";
12001
- 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";
12002
12412
  var CloudSyncStore = class {
12003
12413
  links = /* @__PURE__ */ new Map();
12004
12414
  loaded = false;
12005
12415
  /** Machine-wide: this describes the machine, not any one project. */
12006
12416
  getFilePath() {
12007
- return machineFilePath(FILENAME5);
12417
+ return machineFilePath(FILENAME6);
12008
12418
  }
12009
12419
  /** Where older versions kept it — read only, so an older Runeya keeps working. */
12010
12420
  getLegacyFilePath() {
12011
- return legacyMachineFilePath(FILENAME5);
12421
+ return legacyMachineFilePath(FILENAME6);
12012
12422
  }
12013
12423
  /**
12014
12424
  * Read the machine file, falling back to the launch directory of old.
@@ -12018,9 +12428,9 @@ var CloudSyncStore = class {
12018
12428
  */
12019
12429
  async readFromDisk() {
12020
12430
  try {
12021
- return await readFile14(this.getFilePath(), "utf-8");
12431
+ return await readFile15(this.getFilePath(), "utf-8");
12022
12432
  } catch {
12023
- return readFile14(this.getLegacyFilePath(), "utf-8");
12433
+ return readFile15(this.getLegacyFilePath(), "utf-8");
12024
12434
  }
12025
12435
  }
12026
12436
  async load() {
@@ -12030,24 +12440,26 @@ var CloudSyncStore = class {
12030
12440
  const data = JSON.parse(raw);
12031
12441
  for (const l of data) {
12032
12442
  this.links.set(l.orgId, { orgId: l.orgId, cloudUrl: l.cloudUrl });
12443
+ void stampOrgCloud(l.orgId, l.cloudUrl);
12033
12444
  }
12034
12445
  } catch {
12035
12446
  }
12036
12447
  this.loaded = true;
12037
12448
  }
12038
12449
  async save() {
12039
- await mkdir16(machineRoot(), { recursive: true });
12450
+ await mkdir17(machineRoot(), { recursive: true });
12040
12451
  const filePath = this.getFilePath();
12041
12452
  const tmpPath = filePath + ".tmp";
12042
12453
  const data = JSON.stringify(Array.from(this.links.values()), null, 2);
12043
- await writeFile14(tmpPath, data, "utf-8");
12044
- await rename11(tmpPath, filePath);
12045
- await chmod9(filePath, 384);
12454
+ await writeFile15(tmpPath, data, "utf-8");
12455
+ await rename12(tmpPath, filePath);
12456
+ await chmod10(filePath, 384);
12046
12457
  }
12047
12458
  /** Store (or replace) the sync link for an organization. */
12048
12459
  async upsert(orgId, cloudUrl) {
12049
12460
  await this.load();
12050
12461
  this.links.set(orgId, { orgId, cloudUrl });
12462
+ await stampOrgCloud(orgId, cloudUrl);
12051
12463
  await this.save();
12052
12464
  }
12053
12465
  async remove(orgId) {
@@ -12068,16 +12480,16 @@ var cloudSyncStore = new CloudSyncStore();
12068
12480
 
12069
12481
  // ../server/src/services/cloud-sync-bridge.ts
12070
12482
  import { randomUUID as randomUUID11 } from "crypto";
12071
- import { mkdir as mkdir18, rm as rm5 } from "fs/promises";
12483
+ import { mkdir as mkdir19, rm as rm5 } from "fs/promises";
12072
12484
  import * as Y from "yjs";
12073
12485
 
12074
12486
  // ../server/src/services/org-lock.ts
12075
- 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";
12076
12488
  import { readFileSync, unlinkSync } from "fs";
12077
- import { join as join15 } from "path";
12489
+ import { join as join17 } from "path";
12078
12490
  var held = /* @__PURE__ */ new Set();
12079
12491
  function lockPath(orgId) {
12080
- return join15(orgVaultRoot(orgId), "org.lock");
12492
+ return join17(orgVaultRoot(orgId), "org.lock");
12081
12493
  }
12082
12494
  function isRunning(pid) {
12083
12495
  if (!Number.isInteger(pid) || pid <= 0) return false;
@@ -12090,7 +12502,7 @@ function isRunning(pid) {
12090
12502
  }
12091
12503
  async function readLock(path) {
12092
12504
  try {
12093
- const parsed = JSON.parse(await readFile15(path, "utf-8"));
12505
+ const parsed = JSON.parse(await readFile16(path, "utf-8"));
12094
12506
  return typeof parsed?.pid === "number" ? parsed : null;
12095
12507
  } catch {
12096
12508
  return null;
@@ -12098,7 +12510,7 @@ async function readLock(path) {
12098
12510
  }
12099
12511
  async function acquireOrgLock(orgId) {
12100
12512
  const path = lockPath(orgId);
12101
- await mkdir17(orgVaultRoot(orgId), { recursive: true });
12513
+ await mkdir18(orgVaultRoot(orgId), { recursive: true });
12102
12514
  for (let attempt = 0; attempt < 2; attempt++) {
12103
12515
  try {
12104
12516
  const handle = await open2(path, "wx");
@@ -12165,6 +12577,17 @@ var SyncOutdatedError = class extends Error {
12165
12577
  this.lastIncompatibleVersion = lastIncompatibleVersion;
12166
12578
  }
12167
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
+ };
12168
12591
  var OrgSyncConnection = class {
12169
12592
  doc = new Y.Doc();
12170
12593
  projects = getCollection(this.doc, "projects");
@@ -12481,18 +12904,24 @@ var OrgSyncConnection = class {
12481
12904
  */
12482
12905
  async followProject(projectId) {
12483
12906
  const inDoc = readEntity(this.collection("projects"), projectId) !== null;
12484
- 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 });
12485
12908
  if (existing && !existing.orgId) {
12486
12909
  console.error(
12487
12910
  "[cloud-sync] project %s is already a local project here \u2014 not following it for org %s",
12488
12911
  projectId,
12489
12912
  this.link.orgId
12490
12913
  );
12491
- 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);
12492
12921
  }
12493
12922
  const isLocal = existing?.orgId === this.link.orgId;
12494
12923
  if (!inDoc && !isLocal) return false;
12495
- await mkdir18(projectVaultRoot(this.link.orgId, projectId), { recursive: true });
12924
+ await mkdir19(projectVaultRoot(this.link.orgId, projectId), { recursive: true });
12496
12925
  this.tracked.add(projectId);
12497
12926
  if (inDoc) await this.pullFromDoc([projectId]);
12498
12927
  else await this.reconcileLocalToDoc();
@@ -12507,7 +12936,7 @@ var OrgSyncConnection = class {
12507
12936
  */
12508
12937
  async unfollowProject(projectId) {
12509
12938
  this.tracked.delete(projectId);
12510
- await projectStore.forgetLocally(projectId);
12939
+ await projectStore.forgetLocally({ id: projectId, orgId: this.link.orgId });
12511
12940
  await rm5(projectVaultRoot(this.link.orgId, projectId), { recursive: true, force: true }).catch(() => {
12512
12941
  });
12513
12942
  }
@@ -12589,9 +13018,33 @@ var OrgSyncConnection = class {
12589
13018
  if (result?.created) await this.bootstrapEnvironment(id);
12590
13019
  }
12591
13020
  async deleteLocally(name, id) {
12592
- if (name === "services") await serviceStore.applyRemoteDelete(id);
12593
- else if (name === "environments") await environmentStore.applyRemoteDelete(id);
12594
- 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";
12595
13048
  }
12596
13049
  async bootstrapEnvironment(projectId) {
12597
13050
  try {
@@ -12603,8 +13056,8 @@ var OrgSyncConnection = class {
12603
13056
  description: "Default local environment (immutable)",
12604
13057
  parentIds: [],
12605
13058
  immutable: true
12606
- });
12607
- await projectStore.addEnvironment(projectId, localEnv.id);
13059
+ }, this.link.orgId);
13060
+ await projectStore.addEnvironment({ id: projectId, orgId: this.link.orgId }, localEnv.id);
12608
13061
  } catch (err) {
12609
13062
  console.error("[cloud-sync] env bootstrap failed for project %s:", projectId, err.message);
12610
13063
  }
@@ -12627,7 +13080,7 @@ var OrgSyncConnection = class {
12627
13080
  */
12628
13081
  async reconcileLocalToDoc() {
12629
13082
  if (this.stopped || this.pulling) return;
12630
- const projects = await projectStore.listDiskFormByOrg(this.link.orgId);
13083
+ const projects = (await projectStore.listDiskFormByOrg(this.link.orgId)).filter((p) => !this.refusalFor(p.id));
12631
13084
  const projectIds = projects.map((p) => p.id);
12632
13085
  const serviceIds = [...new Set(projects.flatMap((p) => p.serviceIds ?? []))];
12633
13086
  const [services, environments] = await Promise.all([
@@ -12777,8 +13230,8 @@ var cloudSyncBridge = new CloudSyncBridge();
12777
13230
 
12778
13231
  // ../server/src/services/master-password.ts
12779
13232
  import { randomBytes as randomBytes4, createCipheriv, createDecipheriv } from "crypto";
12780
- import { readFile as readFile16, writeFile as writeFile15, chmod as chmod10, stat as stat5, rename as rename12 } from "fs/promises";
12781
- 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";
12782
13235
  import { Entry } from "@napi-rs/keyring";
12783
13236
  import { hashRaw as argon2HashRaw } from "@node-rs/argon2";
12784
13237
  var KEYRING_SERVICE = "runeya";
@@ -12790,9 +13243,9 @@ var CANARY_PLAINTEXT = "runeya:v1:key-verification";
12790
13243
  var MAX_KEY_FILE_SIZE = 1024;
12791
13244
  var ARGON2_OPTIONS = { memoryCost: 65536, timeCost: 3, parallelism: 4, outputLen: 32 };
12792
13245
  async function readKeyFile(filePath) {
12793
- const s = await stat5(filePath);
13246
+ const s = await stat6(filePath);
12794
13247
  if (s.size > MAX_KEY_FILE_SIZE) throw new Error(`Fichier suspect : ${filePath} (${s.size} octets)`);
12795
- return readFile16(filePath);
13248
+ return readFile17(filePath);
12796
13249
  }
12797
13250
  function aesEncrypt(key, plaintext) {
12798
13251
  const iv = randomBytes4(12);
@@ -12811,7 +13264,7 @@ function aesDecrypt(key, data) {
12811
13264
  }
12812
13265
  async function getPepperFromFile(dataDir) {
12813
13266
  try {
12814
- const raw = await readKeyFile(join16(dataDir, PEPPER_FILENAME));
13267
+ const raw = await readKeyFile(join18(dataDir, PEPPER_FILENAME));
12815
13268
  const hex = raw.toString("utf-8").trim();
12816
13269
  if (/^[0-9a-f]{64}$/i.test(hex)) return Buffer.from(hex, "hex");
12817
13270
  } catch {
@@ -12819,11 +13272,11 @@ async function getPepperFromFile(dataDir) {
12819
13272
  return null;
12820
13273
  }
12821
13274
  async function writePepperToFile(dataDir, pepper) {
12822
- const filePath = join16(dataDir, PEPPER_FILENAME);
13275
+ const filePath = join18(dataDir, PEPPER_FILENAME);
12823
13276
  const tmpPath = filePath + ".tmp";
12824
- await writeFile15(tmpPath, pepper.toString("hex"), "utf-8");
12825
- await chmod10(tmpPath, 384);
12826
- await rename12(tmpPath, filePath);
13277
+ await writeFile16(tmpPath, pepper.toString("hex"), "utf-8");
13278
+ await chmod11(tmpPath, 384);
13279
+ await rename13(tmpPath, filePath);
12827
13280
  }
12828
13281
  function tryKeyringGet() {
12829
13282
  try {
@@ -12870,14 +13323,14 @@ async function writeEncryptedKey(dataDir, masterPassword, encryptionKey) {
12870
13323
  const payload = aesEncrypt(derivedKey, encryptionKey);
12871
13324
  const fileContent = Buffer.concat([salt, payload]);
12872
13325
  const encoded = fileContent.toString("base64");
12873
- const filePath = join16(dataDir, ENCRYPTED_KEY_FILENAME);
13326
+ const filePath = join18(dataDir, ENCRYPTED_KEY_FILENAME);
12874
13327
  const tmpPath = filePath + ".tmp";
12875
- await writeFile15(tmpPath, encoded, "utf-8");
12876
- await chmod10(tmpPath, 384);
12877
- await rename12(tmpPath, filePath);
13328
+ await writeFile16(tmpPath, encoded, "utf-8");
13329
+ await chmod11(tmpPath, 384);
13330
+ await rename13(tmpPath, filePath);
12878
13331
  }
12879
13332
  async function readEncryptedKey(dataDir, masterPassword) {
12880
- const filePath = join16(dataDir, ENCRYPTED_KEY_FILENAME);
13333
+ const filePath = join18(dataDir, ENCRYPTED_KEY_FILENAME);
12881
13334
  const raw = await readKeyFile(filePath);
12882
13335
  const fileContent = Buffer.from(raw.toString("utf-8").trim(), "base64");
12883
13336
  const salt = fileContent.subarray(0, 16);
@@ -12897,13 +13350,13 @@ async function getPepperFromKeychain(dataDir) {
12897
13350
  }
12898
13351
  async function writeVerifyFile(dataDir, encryptionKey) {
12899
13352
  const payload = aesEncrypt(encryptionKey, Buffer.from(CANARY_PLAINTEXT, "utf-8"));
12900
- const filePath = join16(dataDir, VERIFY_FILENAME);
13353
+ const filePath = join18(dataDir, VERIFY_FILENAME);
12901
13354
  const tmpPath = filePath + ".tmp";
12902
- await writeFile15(tmpPath, payload.toString("base64"), "utf-8");
12903
- await rename12(tmpPath, filePath);
13355
+ await writeFile16(tmpPath, payload.toString("base64"), "utf-8");
13356
+ await rename13(tmpPath, filePath);
12904
13357
  }
12905
13358
  async function verifyEncryptionKey(dataDir, encryptionKey) {
12906
- const filePath = join16(dataDir, VERIFY_FILENAME);
13359
+ const filePath = join18(dataDir, VERIFY_FILENAME);
12907
13360
  const raw = await readKeyFile(filePath);
12908
13361
  if (!matchesCanary(raw.toString("utf-8"), encryptionKey)) {
12909
13362
  throw new Error("Cl\xE9 d'encryption incorrecte");
@@ -12919,14 +13372,14 @@ function matchesCanary(canaryBase64, encryptionKey) {
12919
13372
  }
12920
13373
  async function readVerifyFile(dataDir) {
12921
13374
  try {
12922
- return (await readKeyFile(join16(dataDir, VERIFY_FILENAME))).toString("utf-8").trim();
13375
+ return (await readKeyFile(join18(dataDir, VERIFY_FILENAME))).toString("utf-8").trim();
12923
13376
  } catch {
12924
13377
  return null;
12925
13378
  }
12926
13379
  }
12927
13380
  async function hasVerifyFile(dataDir) {
12928
13381
  try {
12929
- await stat5(join16(dataDir, VERIFY_FILENAME));
13382
+ await stat6(join18(dataDir, VERIFY_FILENAME));
12930
13383
  return true;
12931
13384
  } catch {
12932
13385
  return false;
@@ -12934,7 +13387,7 @@ async function hasVerifyFile(dataDir) {
12934
13387
  }
12935
13388
  async function hasEncryptedKeyFile(dataDir) {
12936
13389
  try {
12937
- await stat5(join16(dataDir, ENCRYPTED_KEY_FILENAME));
13390
+ await stat6(join18(dataDir, ENCRYPTED_KEY_FILENAME));
12938
13391
  return true;
12939
13392
  } catch {
12940
13393
  return false;
@@ -12942,6 +13395,20 @@ async function hasEncryptedKeyFile(dataDir) {
12942
13395
  }
12943
13396
 
12944
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
+ }
12945
13412
  async function reconcileOrgCanary(orgId) {
12946
13413
  const key = getEncryptionKey();
12947
13414
  if (!key) {
@@ -13016,8 +13483,15 @@ var cloudSyncRouter = router({
13016
13483
  const link = await cloudSyncStore.get(input.orgId);
13017
13484
  if (link) await cloudSyncBridge.connect(link);
13018
13485
  }
13019
- const pulled = await cloudSyncBridge.followProject(input.orgId, input.projectId);
13020
- 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
+ }
13021
13495
  }),
13022
13496
  /**
13023
13497
  * Stop materializing one project of an org on this machine.
@@ -13034,6 +13508,47 @@ var cloudSyncRouter = router({
13034
13508
  await cloudSyncBridge.unfollowProject(input.orgId, input.projectId);
13035
13509
  return { success: true };
13036
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
+ }),
13037
13552
  /** Stop syncing an org and forget its token. */
13038
13553
  unlink: protectedProcedure.input(z24.object({ orgId: z24.string().min(1).max(128) })).mutation(async ({ input }) => {
13039
13554
  cloudSyncBridge.disconnect(input.orgId);
@@ -13056,6 +13571,104 @@ var cloudSyncRouter = router({
13056
13571
  // borne est exclusive — il faut strictement plus récent que ce numéro.
13057
13572
  lastIncompatibleVersion: states.get(l.orgId)?.lastIncompatibleVersion ?? null
13058
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 };
13059
13672
  })
13060
13673
  });
13061
13674
 
@@ -13083,7 +13696,8 @@ var appRouter = router({
13083
13696
  wiki: wikiRouter,
13084
13697
  config: configRouter,
13085
13698
  cloudSync: cloudSyncRouter,
13086
- cloudAuth: cloudAuthRouter
13699
+ cloudAuth: cloudAuthRouter,
13700
+ filesystem: filesystemRouter
13087
13701
  });
13088
13702
 
13089
13703
  // ../server/src/middleware/error-handler.ts
@@ -13118,7 +13732,7 @@ var MigrationEngine = class {
13118
13732
  var migrationEngine = new MigrationEngine();
13119
13733
 
13120
13734
  // ../server/src/services/file-security.ts
13121
- import { chmod as chmod11, stat as stat6 } from "fs/promises";
13735
+ import { chmod as chmod12, stat as stat8 } from "fs/promises";
13122
13736
  var SENSITIVE_FILES = [
13123
13737
  "agents.json",
13124
13738
  "settings.json",
@@ -13132,11 +13746,11 @@ async function ensureFilePermissions() {
13132
13746
  for (const filename of SENSITIVE_FILES) {
13133
13747
  for (const filepath of [machineFilePath(filename), legacyMachineFilePath(filename)]) {
13134
13748
  try {
13135
- const stats = await stat6(filepath);
13749
+ const stats = await stat8(filepath);
13136
13750
  const mode = stats.mode & 511;
13137
13751
  if (mode !== SECURE_MODE) {
13138
13752
  console.warn("[security] File", filepath, "has mode", mode.toString(8) + ", fixing to 600");
13139
- await chmod11(filepath, SECURE_MODE);
13753
+ await chmod12(filepath, SECURE_MODE);
13140
13754
  }
13141
13755
  } catch (err) {
13142
13756
  if (err.code !== "ENOENT") {
@@ -13147,19 +13761,129 @@ async function ensureFilePermissions() {
13147
13761
  }
13148
13762
  }
13149
13763
 
13764
+ // ../server/src/services/vault-migration.ts
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";
13767
+ var KEY_FILES = [".encryption.key.enc", ".runeya-verify"];
13768
+ var MACHINE_FILES = [
13769
+ "agents.json",
13770
+ "cloud-session.json",
13771
+ "conversations.private.json",
13772
+ "api-keys.json",
13773
+ "api-keys.private.json",
13774
+ "settings.private.json"
13775
+ ];
13776
+ function rekeyValue(value, from, to, counter) {
13777
+ if (typeof value === "string") {
13778
+ if (!isEncryptedValue(value)) return value;
13779
+ let plaintext;
13780
+ try {
13781
+ plaintext = decryptValueWith(from, value);
13782
+ } catch {
13783
+ return value;
13784
+ }
13785
+ counter.n += 1;
13786
+ return encryptValueWith(to, plaintext);
13787
+ }
13788
+ if (Array.isArray(value)) return value.map((v) => rekeyValue(v, from, to, counter));
13789
+ if (value && typeof value === "object") {
13790
+ const out = {};
13791
+ for (const [k, v] of Object.entries(value)) {
13792
+ out[k] = rekeyValue(v, from, to, counter);
13793
+ }
13794
+ return out;
13795
+ }
13796
+ return value;
13797
+ }
13798
+ async function vaultJsonFiles(root) {
13799
+ const entries = await readdir8(root, { withFileTypes: true });
13800
+ return entries.filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => e.name);
13801
+ }
13802
+ async function exists(path) {
13803
+ try {
13804
+ await stat9(path);
13805
+ return true;
13806
+ } catch {
13807
+ return false;
13808
+ }
13809
+ }
13810
+ async function openLegacyVault(legacyRoot, masterPassword) {
13811
+ const key = await readEncryptedKey(legacyRoot, masterPassword);
13812
+ await verifyEncryptionKey(legacyRoot, key);
13813
+ return key;
13814
+ }
13815
+ async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machineKey, now) {
13816
+ const stamp = now.toISOString().replace(/[:.]/g, "-").substring(0, 19);
13817
+ const backupDir = join20(legacyRoot, `backup-${stamp}`);
13818
+ await mkdir20(backupDir, { recursive: true });
13819
+ const jsonFiles = await vaultJsonFiles(legacyRoot);
13820
+ for (const name of [...jsonFiles, ...KEY_FILES]) {
13821
+ if (await exists(join20(legacyRoot, name))) {
13822
+ await copyFile(join20(legacyRoot, name), join20(backupDir, name));
13823
+ }
13824
+ }
13825
+ const rekeyed = [];
13826
+ for (const name of jsonFiles) {
13827
+ const raw = await readFile18(join20(legacyRoot, name), "utf-8");
13828
+ let parsed;
13829
+ try {
13830
+ parsed = JSON.parse(raw);
13831
+ } catch {
13832
+ continue;
13833
+ }
13834
+ const counter = { n: 0 };
13835
+ const converted = rekeyValue(parsed, legacyKey, machineKey, counter);
13836
+ if (counter.n === 0) continue;
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));
13840
+ rekeyed.push({ file: name, values: counter.n });
13841
+ }
13842
+ for (const { file, values } of rekeyed) {
13843
+ const parsed = JSON.parse(await readFile18(join20(legacyRoot, file), "utf-8"));
13844
+ const check = { n: 0 };
13845
+ rekeyValue(parsed, machineKey, machineKey, check);
13846
+ if (check.n !== values) {
13847
+ throw new Error(
13848
+ `V\xE9rification \xE9chou\xE9e pour ${file} : ${check.n} valeur(s) relisible(s) avec la cl\xE9 machine, ${values} attendue(s)`
13849
+ );
13850
+ }
13851
+ }
13852
+ await mkdir20(machineRootDir, { recursive: true });
13853
+ const moved = [];
13854
+ const skipped = [];
13855
+ for (const name of MACHINE_FILES) {
13856
+ const from = join20(legacyRoot, name);
13857
+ if (!await exists(from)) continue;
13858
+ const to = join20(machineRootDir, name);
13859
+ if (await exists(to)) {
13860
+ skipped.push(name);
13861
+ continue;
13862
+ }
13863
+ await copyFile(from, to);
13864
+ await unlink5(from);
13865
+ moved.push(name);
13866
+ }
13867
+ for (const name of KEY_FILES) {
13868
+ const path = join20(legacyRoot, name);
13869
+ if (await exists(path)) await unlink5(path);
13870
+ }
13871
+ return { backupDir, rekeyed, moved, skipped };
13872
+ }
13873
+
13150
13874
  // ../server/src/migrations/migrate-from-legacy.ts
13151
13875
  import { createRequire } from "module";
13152
13876
  import { randomBytes as randomBytes5, createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2 } from "crypto";
13153
13877
  import { randomUUID as randomUUID12 } from "crypto";
13154
13878
  import {
13155
- readFile as readFile17,
13156
- writeFile as writeFile16,
13157
- rename as rename13,
13158
- mkdir as mkdir19,
13879
+ readFile as readFile19,
13880
+ writeFile as writeFile18,
13881
+ rename as rename15,
13882
+ mkdir as mkdir21,
13159
13883
  access as access2,
13160
- readdir as readdir7
13884
+ readdir as readdir9
13161
13885
  } from "fs/promises";
13162
- import { join as join17 } from "path";
13886
+ import { join as join21, dirname as dirname10 } from "path";
13163
13887
  var _require = createRequire(import.meta.url);
13164
13888
  var PARSER_ID_MAP = {
13165
13889
  "stack-monitor-parser-jsons": "native:json",
@@ -13268,7 +13992,7 @@ async function getSodium() {
13268
13992
  return _sodium;
13269
13993
  }
13270
13994
  async function decryptLegacyFile(filePath, key, sodium) {
13271
- const raw = (await readFile17(filePath, "utf-8")).trim();
13995
+ const raw = (await readFile19(filePath, "utf-8")).trim();
13272
13996
  if (!raw || raw === "[]" || raw === "{}") {
13273
13997
  try {
13274
13998
  return JSON.parse(raw);
@@ -13288,14 +14012,12 @@ async function decryptLegacyFile(filePath, key, sodium) {
13288
14012
  }
13289
14013
  async function atomicWrite(path, data) {
13290
14014
  const tmp = path + ".tmp";
13291
- await writeFile16(tmp, JSON.stringify(data, null, 2), "utf-8");
13292
- await rename13(tmp, path);
14015
+ await writeFile18(tmp, JSON.stringify(data, null, 2), "utf-8");
14016
+ await rename15(tmp, path);
13293
14017
  }
13294
14018
  async function runLegacyMigration(dataDir) {
13295
- const { resolve: resolve6 } = await import("path");
13296
- const abCwd = resolve6(process.cwd());
13297
- const legacyRoot = join17(abCwd, env.DATA_DIR);
13298
- const legacyKeyPath = join17(legacyRoot, "dbs", "encryption-key.json");
14019
+ const legacyRoot = env.DATA_DIR;
14020
+ const legacyKeyPath = join21(legacyRoot, "dbs", "encryption-key.json");
13299
14021
  try {
13300
14022
  await access2(legacyKeyPath);
13301
14023
  } catch {
@@ -13303,20 +14025,20 @@ async function runLegacyMigration(dataDir) {
13303
14025
  }
13304
14026
  console.log("[legacy-migration] Legacy Runeya data detected, starting migration\u2026");
13305
14027
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
13306
- const legacyTmp = join17(abCwd, ".runeya-tmp");
13307
- await rename13(legacyRoot, legacyTmp);
13308
- await mkdir19(dataDir, { recursive: true });
13309
- const legacyOld = join17(dataDir, `backup-${timestamp}`);
13310
- await rename13(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);
13311
14033
  console.log(`[legacy-migration] Legacy data backed up to ${legacyOld}`);
13312
- const keyJson = JSON.parse(await readFile17(join17(legacyOld, "dbs", "encryption-key.json"), "utf-8"));
14034
+ const keyJson = JSON.parse(await readFile19(join21(legacyOld, "dbs", "encryption-key.json"), "utf-8"));
13313
14035
  const sodium = await getSodium();
13314
14036
  const sodiumKey = sodium.from_base64(keyJson.encryptionKey);
13315
14037
  const keyBuffer = Buffer.from(sodiumKey);
13316
14038
  const runyeaKeyHex = keyBuffer.toString("hex");
13317
14039
  await setEncryptionKey(runyeaKeyHex);
13318
14040
  console.log("[legacy-migration] Encryption key loaded in memory (temporary)");
13319
- const dbsDir = join17(legacyOld, "dbs");
14041
+ const dbsDir = join21(legacyOld, "dbs");
13320
14042
  async function readEncrypted(filePath) {
13321
14043
  try {
13322
14044
  return await decryptLegacyFile(filePath, sodiumKey, sodium);
@@ -13325,29 +14047,29 @@ async function runLegacyMigration(dataDir) {
13325
14047
  return null;
13326
14048
  }
13327
14049
  }
13328
- const envsDir = join17(dbsDir, "envs");
13329
- const envFiles = (await readdir7(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"));
13330
14052
  const legacyEnvs = {};
13331
14053
  for (const file of envFiles) {
13332
14054
  const name = file.replace(".encrypted.json", "");
13333
- legacyEnvs[name] = await readEncrypted(join17(envsDir, file));
14055
+ legacyEnvs[name] = await readEncrypted(join21(envsDir, file));
13334
14056
  }
13335
- const servicesDir = join17(dbsDir, "services");
13336
- const serviceFiles = (await readdir7(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"));
13337
14059
  const legacyServices = {};
13338
14060
  for (const file of serviceFiles) {
13339
14061
  const name = file.replace(".encrypted.json", "");
13340
14062
  if (!name) continue;
13341
- const data = await readEncrypted(join17(servicesDir, file));
14063
+ const data = await readEncrypted(join21(servicesDir, file));
13342
14064
  if (data?.label) legacyServices[name] = data;
13343
14065
  }
13344
- const overridesDir = join17(dbsDir, "overrides");
13345
- const overrideFiles = (await readdir7(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"));
13346
14068
  const legacyServiceOverrides = {};
13347
14069
  const legacyEnvOverrides = {};
13348
14070
  for (const file of overrideFiles) {
13349
14071
  const baseName = file.replace(".encrypted.json", "");
13350
- const data = await readEncrypted(join17(overridesDir, file));
14072
+ const data = await readEncrypted(join21(overridesDir, file));
13351
14073
  if (!data) continue;
13352
14074
  if (baseName.endsWith("-envs")) {
13353
14075
  legacyServiceOverrides[baseName.slice(0, -5)] = data;
@@ -13355,11 +14077,11 @@ async function runLegacyMigration(dataDir) {
13355
14077
  legacyEnvOverrides[baseName.slice(0, -12)] = data;
13356
14078
  }
13357
14079
  }
13358
- const parsersDir = join17(dbsDir, "parsers");
13359
- const parserFiles = (await readdir7(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"));
13360
14082
  const legacyParsers = [];
13361
14083
  for (const file of parserFiles) {
13362
- const data = await readEncrypted(join17(parsersDir, file));
14084
+ const data = await readEncrypted(join21(parsersDir, file));
13363
14085
  if (data?.id) legacyParsers.push(data);
13364
14086
  }
13365
14087
  const projectMonoId = randomUUID12();
@@ -13700,7 +14422,7 @@ async function runLegacyMigration(dataDir) {
13700
14422
  }
13701
14423
  let existingSettings = {};
13702
14424
  try {
13703
- existingSettings = JSON.parse(await readFile17(join17(dataDir, "settings.json"), "utf-8"));
14425
+ existingSettings = JSON.parse(await readFile19(join21(dataDir, "settings.json"), "utf-8"));
13704
14426
  } catch {
13705
14427
  }
13706
14428
  const migratedParsers = legacyParsers.map((p) => ({
@@ -13721,11 +14443,11 @@ ${p.transform ?? ""}`,
13721
14443
  ...migratedParsers
13722
14444
  ]
13723
14445
  };
13724
- await atomicWrite(join17(dataDir, "projects.json"), runeProjects);
13725
- await atomicWrite(join17(dataDir, "services.json"), runeServices);
13726
- await atomicWrite(join17(dataDir, "environments.json"), runeEnvs);
13727
- await atomicWrite(join17(dataDir, "environment-overrides.json"), flatOverrides);
13728
- await atomicWrite(join17(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);
13729
14451
  const svcEnvCount = runeEnvs.filter((e) => e.scope === "service").length;
13730
14452
  console.log(`[legacy-migration] Written: ${runeProjects.length} projects, ${runeServices.length} services, ${runeEnvs.length} environments (${svcEnvCount} service-scoped)`);
13731
14453
  console.log(`[legacy-migration] Overrides: ${Object.keys(flatOverrides).length}, custom parsers: ${migratedParsers.length} (all disabled \u2014 rewrite required)`);
@@ -13736,12 +14458,12 @@ ${p.transform ?? ""}`,
13736
14458
  }
13737
14459
  async function findLegacyKeyInBackup(dataDir) {
13738
14460
  try {
13739
- const entries = await readdir7(dataDir);
14461
+ const entries = await readdir9(dataDir);
13740
14462
  const backupDirs = entries.filter((e) => e.startsWith("backup-")).sort((a, b) => a < b ? -1 : a > b ? 1 : 0).reverse();
13741
14463
  for (const dir of backupDirs) {
13742
- const keyJsonPath = join17(dataDir, dir, "dbs", "encryption-key.json");
14464
+ const keyJsonPath = join21(dataDir, dir, "dbs", "encryption-key.json");
13743
14465
  try {
13744
- const raw = await readFile17(keyJsonPath, "utf-8");
14466
+ const raw = await readFile19(keyJsonPath, "utf-8");
13745
14467
  const parsed = JSON.parse(raw);
13746
14468
  if (!parsed.encryptionKey) continue;
13747
14469
  const sodium = await getSodium();
@@ -13759,18 +14481,17 @@ async function findLegacyKeyInBackup(dataDir) {
13759
14481
  return null;
13760
14482
  }
13761
14483
  async function runOverrideOnlyMigration(dataDir) {
13762
- const { resolve: resolve6 } = await import("path");
13763
- const abDataDir = resolve6(dataDir);
13764
- const abCwd = resolve6(process.cwd());
14484
+ const { resolve: resolve7 } = await import("path");
14485
+ const abDataDir = resolve7(dataDir);
13765
14486
  const candidateDirs = [
13766
- join17(abCwd, ".runeya", "dbs", "overrides"),
13767
- join17(abDataDir, "dbs", "overrides")
14487
+ join21(dirname10(env.DATA_DIR), ".runeya", "dbs", "overrides"),
14488
+ join21(abDataDir, "dbs", "overrides")
13768
14489
  ];
13769
14490
  let overridesDir = null;
13770
14491
  let overrideFiles = [];
13771
14492
  for (const dir of candidateDirs) {
13772
14493
  try {
13773
- const files = (await readdir7(dir)).filter((f) => f.endsWith(".encrypted.json"));
14494
+ const files = (await readdir9(dir)).filter((f) => f.endsWith(".encrypted.json"));
13774
14495
  if (files.length > 0) {
13775
14496
  overridesDir = dir;
13776
14497
  overrideFiles = files;
@@ -13780,7 +14501,7 @@ async function runOverrideOnlyMigration(dataDir) {
13780
14501
  }
13781
14502
  }
13782
14503
  if (!overridesDir || overrideFiles.length === 0) return false;
13783
- const legacyKeyPath = join17(overridesDir, "..", "encryption-key.json");
14504
+ const legacyKeyPath = join21(overridesDir, "..", "encryption-key.json");
13784
14505
  try {
13785
14506
  await access2(legacyKeyPath);
13786
14507
  return false;
@@ -13798,7 +14519,7 @@ async function runOverrideOnlyMigration(dataDir) {
13798
14519
  const envNameToId = {};
13799
14520
  let envsRaw;
13800
14521
  try {
13801
- envsRaw = JSON.parse(await readFile17(join17(abDataDir, "environments.json"), "utf-8"));
14522
+ envsRaw = JSON.parse(await readFile19(join21(abDataDir, "environments.json"), "utf-8"));
13802
14523
  for (const _env of envsRaw) {
13803
14524
  const env2 = _env;
13804
14525
  if (env2["name"] && env2["id"]) {
@@ -13817,7 +14538,7 @@ async function runOverrideOnlyMigration(dataDir) {
13817
14538
  let servicesRaw = [];
13818
14539
  const serviceNameToId = {};
13819
14540
  try {
13820
- servicesRaw = JSON.parse(await readFile17(join17(abDataDir, "services.json"), "utf-8"));
14541
+ servicesRaw = JSON.parse(await readFile19(join21(abDataDir, "services.json"), "utf-8"));
13821
14542
  for (const _svc of servicesRaw) {
13822
14543
  const svc = _svc;
13823
14544
  if (svc["name"] && svc["id"]) serviceNameToId[svc["name"]] = svc["id"];
@@ -13834,7 +14555,7 @@ async function runOverrideOnlyMigration(dataDir) {
13834
14555
  const envName = baseName.slice(0, -"-environment".length);
13835
14556
  let data;
13836
14557
  try {
13837
- data = await decryptLegacyFile(join17(overridesDir, file), sodiumKey, sodium);
14558
+ data = await decryptLegacyFile(join21(overridesDir, file), sodiumKey, sodium);
13838
14559
  } catch (err) {
13839
14560
  console.warn(`[override-migration] Failed to decrypt ${file}: ${err.message}`);
13840
14561
  continue;
@@ -13861,7 +14582,7 @@ async function runOverrideOnlyMigration(dataDir) {
13861
14582
  }
13862
14583
  }
13863
14584
  if (Object.keys(flatOverrides).length > 0) {
13864
- await atomicWrite(join17(abDataDir, "environment-overrides.json"), flatOverrides);
14585
+ await atomicWrite(join21(abDataDir, "environment-overrides.json"), flatOverrides);
13865
14586
  console.log(`[override-migration] Written ${Object.keys(flatOverrides).length} override(s) to environment-overrides.json`);
13866
14587
  }
13867
14588
  let envsModified = false;
@@ -13871,7 +14592,7 @@ async function runOverrideOnlyMigration(dataDir) {
13871
14592
  const serviceName = baseName.slice(0, -"-envs".length);
13872
14593
  let data;
13873
14594
  try {
13874
- data = await decryptLegacyFile(join17(overridesDir, file), sodiumKey, sodium);
14595
+ data = await decryptLegacyFile(join21(overridesDir, file), sodiumKey, sodium);
13875
14596
  } catch (err) {
13876
14597
  console.warn(`[override-migration] Failed to decrypt ${file}: ${err.message}`);
13877
14598
  continue;
@@ -13930,12 +14651,12 @@ async function runOverrideOnlyMigration(dataDir) {
13930
14651
  }
13931
14652
  }
13932
14653
  if (envsModified) {
13933
- await atomicWrite(join17(abDataDir, "environments.json"), envsRaw);
14654
+ await atomicWrite(join21(abDataDir, "environments.json"), envsRaw);
13934
14655
  console.log("[override-migration] Merged service overrides into environments.json");
13935
14656
  }
13936
14657
  const migratedDir = overridesDir.replace(/overrides$/, "overrides-migrated");
13937
14658
  try {
13938
- await rename13(overridesDir, migratedDir);
14659
+ await rename15(overridesDir, migratedDir);
13939
14660
  console.log(`[override-migration] Moved overrides/ \u2192 overrides-migrated/`);
13940
14661
  } catch (err) {
13941
14662
  console.warn(`[override-migration] Could not rename overrides dir: ${err.message}`);
@@ -13945,8 +14666,8 @@ async function runOverrideOnlyMigration(dataDir) {
13945
14666
  }
13946
14667
 
13947
14668
  // ../server/src/services/server-state.ts
13948
- import { join as join18 } from "path";
13949
- import { stat as stat7 } from "fs/promises";
14669
+ import { join as join22 } from "path";
14670
+ import { stat as stat10 } from "fs/promises";
13950
14671
  var ENCRYPTED_KEY_FILENAME2 = ".encryption.key.enc";
13951
14672
  var VERIFY_FILENAME2 = ".runeya-verify";
13952
14673
  async function getServerState() {
@@ -13955,36 +14676,43 @@ async function getServerState() {
13955
14676
  let hasVerify = false;
13956
14677
  let hasData = false;
13957
14678
  try {
13958
- await stat7(join18(dataDir, ENCRYPTED_KEY_FILENAME2));
14679
+ await stat10(join22(dataDir, ENCRYPTED_KEY_FILENAME2));
13959
14680
  hasEncFile = true;
13960
14681
  } catch {
13961
14682
  }
13962
14683
  try {
13963
- await stat7(join18(dataDir, VERIFY_FILENAME2));
14684
+ await stat10(join22(dataDir, VERIFY_FILENAME2));
13964
14685
  hasVerify = true;
13965
14686
  } catch {
13966
14687
  }
13967
14688
  try {
13968
- await stat7(join18(dataDir, "projects.json"));
14689
+ await stat10(join22(dataDir, "projects.json"));
13969
14690
  hasData = true;
13970
14691
  } catch {
13971
14692
  }
13972
14693
  try {
13973
- await stat7(join18(dataDir, "project.json"));
14694
+ await stat10(join22(dataDir, "project.json"));
13974
14695
  hasData = true;
13975
14696
  } catch {
13976
14697
  }
13977
14698
  if (hasEncryptionKey()) {
13978
- return { state: "unlocked", hasVerifyFile: false, isLegacyMigration: false, hasExistingData: hasData };
14699
+ return {
14700
+ state: "unlocked",
14701
+ hasVerifyFile: false,
14702
+ isLegacyMigration: false,
14703
+ hasExistingData: hasData,
14704
+ legacyVault: await legacyVaultRoot()
14705
+ };
13979
14706
  }
13980
14707
  if (hasEncFile) {
13981
- return { state: "locked", hasVerifyFile: hasVerify, isLegacyMigration: false, hasExistingData: hasData };
14708
+ return { state: "locked", hasVerifyFile: hasVerify, isLegacyMigration: false, hasExistingData: hasData, legacyVault: null };
13982
14709
  }
13983
14710
  return {
13984
14711
  state: "setup",
13985
14712
  hasVerifyFile: hasVerify,
13986
14713
  isLegacyMigration: hasPendingLegacyKey(),
13987
- hasExistingData: hasData
14714
+ hasExistingData: hasData,
14715
+ legacyVault: null
13988
14716
  };
13989
14717
  }
13990
14718
 
@@ -14368,7 +15096,7 @@ async function createLocalServer() {
14368
15096
  console.log("[server] Premier lancement \u2014 setup requis");
14369
15097
  }
14370
15098
  }
14371
- const absoluteDataDir = resolve5(env.DATA_DIR);
15099
+ const absoluteDataDir = resolve6(env.DATA_DIR);
14372
15100
  console.log(`[server] Local project directory: ${absoluteDataDir}`);
14373
15101
  console.log(`[server] Machine directory: ${machineRoot()}`);
14374
15102
  if (existsSync2(env.DATA_DIR)) {
@@ -14416,9 +15144,10 @@ async function createLocalServer() {
14416
15144
  parentIds: [],
14417
15145
  color: "#22c55e",
14418
15146
  immutable: true
14419
- });
15147
+ }, project.orgId);
14420
15148
  await projectStore.update({
14421
15149
  id: project.id,
15150
+ orgId: project.orgId,
14422
15151
  environmentIds: [localEnv.id],
14423
15152
  activeEnvironmentId: localEnv.id
14424
15153
  });
@@ -14730,6 +15459,48 @@ async function createLocalServer() {
14730
15459
  }
14731
15460
  }
14732
15461
  });
15462
+ app.post("/api/migrate-vault", authLimiter, csrfMiddleware, express.json({ limit: "10kb" }), async (req, res) => {
15463
+ try {
15464
+ const jwt = typeof req.headers.authorization === "string" && req.headers.authorization.startsWith("Bearer ") ? req.headers.authorization.slice(7) : "";
15465
+ if (!isCiMode()) {
15466
+ try {
15467
+ await verifyJwt(jwt);
15468
+ } catch {
15469
+ res.status(401).json({ error: "Session invalide" });
15470
+ return;
15471
+ }
15472
+ }
15473
+ const machineKey = getEncryptionKey();
15474
+ if (!machineKey) {
15475
+ res.status(409).json({ error: "Serveur verrouill\xE9" });
15476
+ return;
15477
+ }
15478
+ const legacyRoot = await legacyVaultRoot();
15479
+ if (!legacyRoot) {
15480
+ res.status(409).json({ error: "Aucun coffre \xE0 migrer dans ce r\xE9pertoire" });
15481
+ return;
15482
+ }
15483
+ const body = req.body;
15484
+ const masterPassword = body.masterPassword;
15485
+ if (typeof masterPassword !== "string" || masterPassword.length < 1 || masterPassword.length > 1024) {
15486
+ res.status(400).json({ error: "masterPassword invalide" });
15487
+ return;
15488
+ }
15489
+ let legacyKey;
15490
+ try {
15491
+ legacyKey = await openLegacyVault(legacyRoot, masterPassword);
15492
+ } catch {
15493
+ res.status(401).json({ error: "Ancien master password incorrect" });
15494
+ return;
15495
+ }
15496
+ const report = await migrateLegacyVault(legacyRoot, machineRoot(), legacyKey, machineKey, /* @__PURE__ */ new Date());
15497
+ console.log(`[vault-migration] ${legacyRoot} migr\xE9 \u2014 sauvegarde : ${report.backupDir}`);
15498
+ res.json(report);
15499
+ } catch (err) {
15500
+ console.error("[server] Vault migration error:", err);
15501
+ res.status(500).json({ error: err instanceof Error ? err.message : "Erreur interne" });
15502
+ }
15503
+ });
14733
15504
  app.get("/:apikey/llms.txt", llmsLimiter, async (req, res) => {
14734
15505
  const apikey = Array.isArray(req.params["apikey"]) ? req.params["apikey"][0] : req.params["apikey"];
14735
15506
  if (!apikey || !/^[0-9a-f]{64}$/i.test(apikey)) {
@@ -14964,12 +15735,12 @@ async function createLocalServer() {
14964
15735
  ]);
14965
15736
  server.closeAllConnections();
14966
15737
  await Promise.race([
14967
- new Promise((resolve6) => wss.close(() => resolve6())),
14968
- new Promise((resolve6) => setTimeout(resolve6, 2e3))
15738
+ new Promise((resolve7) => wss.close(() => resolve7())),
15739
+ new Promise((resolve7) => setTimeout(resolve7, 2e3))
14969
15740
  ]);
14970
15741
  await Promise.race([
14971
- new Promise((resolve6) => server.close(() => resolve6())),
14972
- new Promise((resolve6) => setTimeout(resolve6, 2e3))
15742
+ new Promise((resolve7) => server.close(() => resolve7())),
15743
+ new Promise((resolve7) => setTimeout(resolve7, 2e3))
14973
15744
  ]);
14974
15745
  console.log("[server] Shutdown complete");
14975
15746
  };
@@ -14990,7 +15761,7 @@ var PullEnvError = class extends Error {
14990
15761
  };
14991
15762
  async function promptPassword() {
14992
15763
  process.stderr.write("Master password: ");
14993
- return new Promise((resolve6) => {
15764
+ return new Promise((resolve7) => {
14994
15765
  const stdin = process.stdin;
14995
15766
  const wasRaw = stdin.isRaw;
14996
15767
  if (stdin.isTTY) stdin.setRawMode(true);
@@ -15003,7 +15774,7 @@ async function promptPassword() {
15003
15774
  stdin.pause();
15004
15775
  stdin.removeListener("data", onData);
15005
15776
  process.stderr.write("\n");
15006
- resolve6(input);
15777
+ resolve7(input);
15007
15778
  } else if (ch === "") {
15008
15779
  process.exit(1);
15009
15780
  } else if (ch === "\x7F" || ch === "\b") {
@@ -15066,7 +15837,7 @@ if (process.env["RUNEYA_DATA_DIR"]) {
15066
15837
  console.log(`[server] Working directory: ${resolved} (from RUNEYA_DATA_DIR)`);
15067
15838
  }
15068
15839
  function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500) {
15069
- return new Promise((resolve6, reject) => {
15840
+ return new Promise((resolve7, reject) => {
15070
15841
  let attempts = 0;
15071
15842
  const wssErrorHandler = () => {
15072
15843
  };
@@ -15087,7 +15858,7 @@ function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500)
15087
15858
  server.listen(port, host, () => {
15088
15859
  server.removeListener("error", onError);
15089
15860
  wss?.removeListener("error", wssErrorHandler);
15090
- resolve6();
15861
+ resolve7();
15091
15862
  });
15092
15863
  };
15093
15864
  tryListen();
@@ -15134,4 +15905,4 @@ export {
15134
15905
  createLocalServer,
15135
15906
  pullEnv
15136
15907
  };
15137
- //# sourceMappingURL=src-7CJJBMY3.js.map
15908
+ //# sourceMappingURL=src-5QPLS5TQ.js.map