@runeya/runeya 2.0.146 → 2.0.148

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 (171) hide show
  1. package/agent/index.js +10 -4
  2. package/agent/index.js.map +1 -1
  3. package/{agent-manager-R7BPQO6D.js → agent-manager-46C4NIQ2.js} +4 -2
  4. package/{chat-LPML22J5.js → chat-CMNDMXIP.js} +9 -5
  5. package/{chunk-SEPAURTQ.js → chunk-2NE52CL5.js} +156 -923
  6. package/chunk-2NE52CL5.js.map +1 -0
  7. package/chunk-4D3GCBPO.js +462 -0
  8. package/chunk-4D3GCBPO.js.map +1 -0
  9. package/{chunk-KV4GJKXU.js → chunk-5XG3X2TM.js} +27 -1479
  10. package/chunk-5XG3X2TM.js.map +1 -0
  11. package/chunk-7QXP4FXD.js +969 -0
  12. package/chunk-7QXP4FXD.js.map +1 -0
  13. package/chunk-DA2DZBTL.js +43 -0
  14. package/chunk-DA2DZBTL.js.map +1 -0
  15. package/chunk-DFPZ4MTI.js +1041 -0
  16. package/chunk-DFPZ4MTI.js.map +1 -0
  17. package/{chunk-UD42LACH.js → chunk-JNK6H5KH.js} +164 -1007
  18. package/chunk-JNK6H5KH.js.map +1 -0
  19. package/chunk-WDHLEO5S.js +809 -0
  20. package/chunk-WDHLEO5S.js.map +1 -0
  21. package/{companion-W3UMZWGH.js → companion-XKMNRIJB.js} +8 -3
  22. package/environment-resolver-ZDTF3MHI.js +18 -0
  23. package/environment-resolver-ZDTF3MHI.js.map +1 -0
  24. package/index.js +7 -7
  25. package/{install-gate-PVMGJDWE.js → install-gate-PWPGC63Z.js} +2 -2
  26. package/mcp-ready-TAPBAOZY.js +9 -0
  27. package/mcp-ready-TAPBAOZY.js.map +1 -0
  28. package/mcp-server/index.js +24 -0
  29. package/mcp-server/index.js.map +1 -1
  30. package/mcp-server/launch.d.ts +6 -0
  31. package/mcp-server/launch.js +2 -1
  32. package/mcp-server/launch.js.map +1 -1
  33. package/package.json +1 -1
  34. package/project-store-FLXTAD3J.js +23 -0
  35. package/project-store-FLXTAD3J.js.map +1 -0
  36. package/{src-LEDGW5KP.js → src-I5TPC6GW.js} +155 -63
  37. package/src-I5TPC6GW.js.map +1 -0
  38. package/web/assets/{AgentScalarChatInterface.vue-BYszptaB.js → AgentScalarChatInterface.vue-DKBinB_l.js} +2 -2
  39. package/web/assets/AgentTerminalPage-CixU7x6O.js +1 -0
  40. package/web/assets/AiChatModals-BvGUd-QD.css +1 -0
  41. package/web/assets/AiChatModals-C-hXImkO.js +4 -0
  42. package/web/assets/AppLayout-At9wk0sx.js +1 -0
  43. package/web/assets/{AppLayout-tsw5Ow0w.css → AppLayout-BlhwflMq.css} +1 -1
  44. package/web/assets/AuditPage-BkqbHxjY.js +1 -0
  45. package/web/assets/CloudAccountPage-D5C2SCS9.js +1 -0
  46. package/web/assets/CloudMessagingPage-Bi57GGHO.js +2 -0
  47. package/web/assets/DashboardPage-dEQop9ql.js +1 -0
  48. package/web/assets/DirectoryPickerDialog-DIMSeDhw.js +1 -0
  49. package/web/assets/{EditorIcon-CypGLV6w.js → EditorIcon-W3Szca4J.js} +1 -1
  50. package/web/assets/EnvironmentForm-C5emavNh.js +1 -0
  51. package/web/assets/EnvironmentFormPage-BIoy8fsW.js +1 -0
  52. package/web/assets/EnvironmentListPage-BDYeh7XF.js +1 -0
  53. package/web/assets/{FlagChip-R_T5qYpL.js → FlagChip-DPtIBCjQ.js} +1 -1
  54. package/web/assets/KanbanBoardPage-BlCcjBwV.js +4 -0
  55. package/web/assets/KanbanListPage-DbXONxfC.js +1 -0
  56. package/web/assets/LaunchProjectPage-Cg92hHwQ.js +1 -0
  57. package/web/assets/LaunchServicesPage-CO8gOB9D.js +1 -0
  58. package/web/assets/LaunchServicesPage-h5o3zLcy.css +1 -0
  59. package/web/assets/{LaunchShell-DUrSunB4.js → LaunchShell-CDb21ihZ.js} +1 -1
  60. package/web/assets/LogViewer-DsusKk2b.js +3 -0
  61. package/web/assets/OrgKeyPrompt-CX9K7r9I.js +1 -0
  62. package/web/assets/ProcessControls-B7hWVqiR.css +1 -0
  63. package/web/assets/ProcessControls-TEZutuxq.js +1 -0
  64. package/web/assets/ProcessDetailPage-Cj23FECq.js +1 -0
  65. package/web/assets/ProcessMetrics-1P6iE2Il.js +1 -0
  66. package/web/assets/ProjectCreateDialog-D3aMWKfw.js +1 -0
  67. package/web/assets/{ProjectLocationGate-DhMVdIjN.js → ProjectLocationGate-DU0Jj8Nw.js} +1 -1
  68. package/web/assets/ProjectPublishDialog-D7sNKei3.js +1 -0
  69. package/web/assets/{RandomJobTestPage-BUS2Ty6-.js → RandomJobTestPage-DQUMRHyf.js} +1 -1
  70. package/web/assets/{ScenarioEditorPage-vMoRt0Qn.css → ScenarioEditorPage-BSjSX4bw.css} +1 -1
  71. package/web/assets/ScenarioEditorPage-DvCdcY9m.js +14 -0
  72. package/web/assets/SendToChatDialog-BdWKjYmv.js +1 -0
  73. package/web/assets/ServiceControlsBar-B_J5m-Kc.js +1 -0
  74. package/web/assets/ServiceDetailPage-DhJcjFfY.js +2 -0
  75. package/web/assets/{ServiceDetailPage-COdaCZ6r.css → ServiceDetailPage-DnSf5cm9.css} +1 -1
  76. package/web/assets/ServiceFormPage-fp8NCHSv.js +1 -0
  77. package/web/assets/{SettingsPage-4pKUgKPm.css → SettingsPage-NH2fQap4.css} +1 -1
  78. package/web/assets/SettingsPage-s28BodWe.js +2086 -0
  79. package/web/assets/UnlockPage-BXXIPHW_.js +1 -0
  80. package/web/assets/VaultMigrationPage-BfNuKtkJ.js +1 -0
  81. package/web/assets/{VersionBadge-BmArk24p.js → VersionBadge-CW7czacg.js} +1 -1
  82. package/web/assets/{WikiPage-DADu8WUA.js → WikiPage-BiwpCM3E.js} +1 -1
  83. package/web/assets/WikiView-CMch_06Z.js +1 -0
  84. package/web/assets/{add-scalar-classes-dWEWPlFD.js → add-scalar-classes-Ckanjq1h.js} +14 -14
  85. package/web/assets/{agent.store-CKLAkF9a.js → agent.store-piokQ9Dt.js} +1 -1
  86. package/web/assets/{auth-Cg9BWTfK.js → auth-BT_jf-5e.js} +2 -2
  87. package/web/assets/cloud-chat.store-CDKh4tIy.js +1 -0
  88. package/web/assets/{cloud-client-D_zCiW0s.js → cloud-client-CzPdZLDq.js} +2 -2
  89. package/web/assets/{cloud-session-DOaDpZTy.js → cloud-session-BYfn6YM4.js} +2 -2
  90. package/web/assets/floating-ui.dom-DF3oyfHd.js +1 -0
  91. package/web/assets/i18n-D0FT7rCj.js +1 -0
  92. package/web/assets/index-3LTzgeU-.css +1 -0
  93. package/web/assets/index-Ci2CNnit.js +309 -0
  94. package/web/assets/modal-iRaP1izE.js +5 -0
  95. package/web/assets/notification.store-CvvuTBVz.js +1 -0
  96. package/web/assets/{project-launch.store-y7JBMNiE.js → project-launch.store-CTmbl8UY.js} +1 -1
  97. package/web/assets/{project.store-DGJr0Qr9.js → project.store-CNC6_4r4.js} +1 -1
  98. package/web/assets/runtime-dom.esm-bundler-CyEVRK6B.js +1 -0
  99. package/web/assets/scenario.store-BIFQPXgl.js +7 -0
  100. package/web/assets/{service.store-5jWsJxPx.js → service.store-B40U4QzI.js} +1 -1
  101. package/web/assets/settings-scope-CyWMCjRY.js +1 -0
  102. package/web/assets/settings.store-Dw6bAoWE.js +1 -0
  103. package/web/assets/{src-Dnwk6NpV.js → src-CrNi4Pjg.js} +3 -3
  104. package/web/assets/src-DIBHat7I.js +3 -0
  105. package/web/assets/src-DtCW_XCh.js +1 -0
  106. package/web/assets/{trpc-Drabzwpd.js → trpc-BAZGRZBD.js} +3 -3
  107. package/web/assets/{useCloudOrg-BAQEtFPl.js → useCloudOrg-EnXFBwt1.js} +1 -1
  108. package/web/assets/useEnvVarAutocomplete-BxJh2Lw0.js +1 -0
  109. package/web/assets/useProjectCatalog-BrvUGv9A.js +1 -0
  110. package/web/assets/{useProjectCloudOrg-CEN9-_OA.js → useProjectCloudOrg-Czxlg00k.js} +1 -1
  111. package/web/assets/{useServiceUrlHost-BuoPEXZX.js → useServiceUrlHost-Szz9y3rh.js} +1 -1
  112. package/web/assets/vue-i18n-o7yraQpR.js +3 -0
  113. package/web/index.html +21 -21
  114. package/chunk-KV4GJKXU.js.map +0 -1
  115. package/chunk-SEPAURTQ.js.map +0 -1
  116. package/chunk-UD42LACH.js.map +0 -1
  117. package/src-LEDGW5KP.js.map +0 -1
  118. package/web/assets/AgentTerminalPage-C3TJ1NL9.js +0 -1
  119. package/web/assets/AiChatModals-D3scRE3S.js +0 -4
  120. package/web/assets/AiChatModals-EfRuvId7.css +0 -1
  121. package/web/assets/AppLayout-BMQUjPKe.js +0 -1
  122. package/web/assets/AuditPage-CKcExBws.js +0 -1
  123. package/web/assets/CloudAccountPage-B6oPg0BZ.js +0 -1
  124. package/web/assets/CloudMessagingPage-C5fwVe-F.js +0 -2
  125. package/web/assets/DashboardPage-9RV7sx3e.js +0 -1
  126. package/web/assets/DirectoryPickerDialog-C06BXyfJ.js +0 -1
  127. package/web/assets/EnvironmentForm-CAvDoira.js +0 -1
  128. package/web/assets/EnvironmentFormPage-BmAxhN-m.js +0 -1
  129. package/web/assets/EnvironmentListPage-B8uATPBI.js +0 -1
  130. package/web/assets/KanbanBoardPage-CQsOkRUf.js +0 -4
  131. package/web/assets/KanbanListPage-CWZqkjD6.js +0 -1
  132. package/web/assets/LaunchProjectPage-saSmC7Pr.js +0 -1
  133. package/web/assets/LaunchServicesPage-BkKyLMEz.js +0 -1
  134. package/web/assets/LaunchServicesPage-DoLDPqQq.css +0 -1
  135. package/web/assets/LogViewer-CZBYkH9J.js +0 -3
  136. package/web/assets/OrgKeyPrompt-DzQ7pwbs.js +0 -1
  137. package/web/assets/ProcessControls-BEeoNlNV.js +0 -1
  138. package/web/assets/ProcessControls-BlpnVylq.css +0 -1
  139. package/web/assets/ProcessDetailPage-CQgu5uKm.js +0 -1
  140. package/web/assets/ProcessMetrics-Crlfkelj.js +0 -1
  141. package/web/assets/ProjectCreateDialog-BolrhRD-.js +0 -1
  142. package/web/assets/ProjectPublishDialog-DsZNWrfz.js +0 -1
  143. package/web/assets/ScenarioEditorPage-MIAGh2us.js +0 -14
  144. package/web/assets/SendToChatDialog-BxWsyv29.js +0 -1
  145. package/web/assets/ServiceControlsBar-DqDPAZt7.js +0 -1
  146. package/web/assets/ServiceDetailPage-hkSAzZ-l.js +0 -2
  147. package/web/assets/ServiceFormPage-DXqhqXg8.js +0 -1
  148. package/web/assets/SettingsPage-DS2YhMIM.js +0 -2086
  149. package/web/assets/UnlockPage-s8SgVcKk.js +0 -1
  150. package/web/assets/VaultMigrationPage-s0b44MDM.js +0 -1
  151. package/web/assets/WikiView-DtGwIt_Q.js +0 -1
  152. package/web/assets/cloud-chat.store-b-BHGz7I.js +0 -1
  153. package/web/assets/editor.store-DCAZSeVG.js +0 -1
  154. package/web/assets/floating-ui.dom-DWhsZyqT.js +0 -1
  155. package/web/assets/i18n-D0afQEGs.js +0 -1
  156. package/web/assets/index-D8VRbyoP.js +0 -309
  157. package/web/assets/index-DsezNbSM.css +0 -1
  158. package/web/assets/modal-qYgTMkYb.js +0 -5
  159. package/web/assets/scenario.store-YWHL2_wu.js +0 -7
  160. package/web/assets/settings-scope-Bv7QGPrt.js +0 -1
  161. package/web/assets/settings.store-bEsB7oKb.js +0 -1
  162. package/web/assets/src--GO-VSJ0.js +0 -5
  163. package/web/assets/src-C-D_y7ou.js +0 -1
  164. package/web/assets/useEnvVarAutocomplete-BqGTODUv.js +0 -1
  165. package/web/assets/useProjectCatalog-yocjIKt6.js +0 -1
  166. package/web/assets/vue-draggable-plus-BHsveB8w.css +0 -1
  167. package/web/assets/vue-draggable-plus-DX4rTBiu.js +0 -1
  168. /package/{agent-manager-R7BPQO6D.js.map → agent-manager-46C4NIQ2.js.map} +0 -0
  169. /package/{chat-LPML22J5.js.map → chat-CMNDMXIP.js.map} +0 -0
  170. /package/{companion-W3UMZWGH.js.map → companion-XKMNRIJB.js.map} +0 -0
  171. /package/{install-gate-PVMGJDWE.js.map → install-gate-PWPGC63Z.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../server/src/services/project-store.ts","../../server/src/utils/ensure-data-gitignore.ts","../../server/src/services/conversation-section-store.ts","../../server/src/services/project-path-store.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { EventEmitter } from 'node:events';\nimport { mkdir, readFile, rename, rm } from 'node:fs/promises';\nimport { dirname, join, resolve } from 'node:path';\nimport type { Project, CreateProject, UpdateProject } from '@runeya/packages-shared';\nimport { ProjectSchema } from '@runeya/packages-shared';\nimport { environmentStore } from './environment-store.js';\nimport { serviceStore } from './service-store.js';\nimport { ensureDataGitignore } from '../utils/ensure-data-gitignore.js';\nimport {\n localVaultRoot,\n listCloudVaults,\n vaultFor,\n localVaultCollidesWithMachineRoot,\n readLocalProjectDirs,\n registerLocalProjectDir,\n unregisterLocalProjectDir,\n localProjectVaultRoot,\n setLocalProjectRoot,\n forgetLocalProjectRoot,\n collidesWithMachineRoot,\n} from './vault-resolver.js';\nimport { registerProjectLoader, setProjectVault, setProjectServices, forgetProject } from './vault-registry.js';\nimport { projectKey, AmbiguousProjectError, type ProjectRef } from './project-key.js';\nimport { conversationSectionStore } from './conversation-section-store.js';\nimport { projectPathStore, holdsRuneyaProject } from './project-path-store.js';\nimport { fingerprintFiles } from './disk-freshness.js';\nimport { atomicWriteSecure } from './run-store-utils.js';\nimport { StoreDiskCoordinator } from './store-disk-coordinator.js';\nimport { recordDetached } from './local-removals.js';\n\n/**\n * One project per vault, so the file holds a single object — in the local vault\n * as in a cloud one.\n */\nconst FILENAME = 'project.json';\n/**\n * The local file before a vault meant one project. Read once, migrated to\n * `project.json`, and kept on disk under a `.bak` name rather than deleted:\n * nothing is thrown away on the user's behalf.\n */\nconst LEGACY_FILENAME = 'projects.json';\n\n/**\n * A project as persisted: `activeEnvironmentId` is runtime-only and never hits\n * disk. This is also exactly what the cloud mirrors — the sync shares what git\n * shares, so \"is it persisted?\" is the only question that decides it.\n */\nfunction toDiskForm(project: Project): Omit<Project, 'activeEnvironmentId'> {\n const { activeEnvironmentId: _runtimeOnly, ...rest } = project;\n return rest;\n}\n\n/**\n * What the launch directory holds: its own project, plus any cloud projects an\n * older version left in the legacy list.\n *\n * Reads `project.json`, and falls back to the `projects.json` of older versions\n * so an existing directory keeps working. A legacy file describing several\n * local projects has no valid placement under one-project-per-vault, so it is\n * refused loudly instead of being silently narrowed to its first entry — losing\n * a project quietly is worse than refusing to start.\n *\n * Cloud projects found in that list are handed back too, so they can be moved\n * into their own vault rather than quietly dropped from the machine.\n */\nasync function readLocalProject(): Promise<{ local: Project | null; strayCloud: Project[] }> {\n const root = localVaultRoot();\n let current: Project | null = null;\n try {\n const raw = await readFile(join(root, FILENAME), 'utf-8');\n const parsed = ProjectSchema.safeParse(JSON.parse(raw));\n if (parsed.success) current = parsed.data;\n } catch {\n // Fall through to the legacy list.\n }\n\n let legacy: Project[];\n try {\n legacy = JSON.parse(await readFile(join(root, LEGACY_FILENAME), 'utf-8')) as Project[];\n } catch {\n return { local: current, strayCloud: [] };\n }\n\n const strayCloud = legacy.filter((p) => p.orgId);\n if (current) return { local: current, strayCloud };\n\n // Cloud projects in the legacy list live in their own vault now; only the\n // local ones compete for this directory.\n const locals = legacy.filter((p) => !p.orgId);\n if (locals.length > 1) {\n const names = locals.map((p) => `\"${p.name}\" (${p.id})`).join(', ');\n throw new Error(\n `${join(root, LEGACY_FILENAME)} holds ${locals.length} local projects: ${names}. ` +\n 'A directory now holds a single local project — move the others to their own directory, ' +\n 'or into an organization, then start again.',\n );\n }\n return { local: locals[0] ?? null, strayCloud };\n}\n\n/**\n * Un coffre se retrouve-t-il depuis son dossier ? Seulement s'il s'appelle\n * `.runeya`. Un `--data-dir` nommé autrement reste le coffre du dossier de\n * lancement et n'entre pas dans la liste de la machine.\n */\nfunction isRegistrable(root: string): boolean {\n return localProjectVaultRoot(dirname(root)) === resolve(root);\n}\n\n/**\n * Le projet local rangé dans ce coffre, ou `null`.\n *\n * Un `project.json` qui nomme une organisation n'est pas un projet local : il\n * est ignoré ici plutôt que pris pour un autre.\n */\nasync function readLocalProjectAt(root: string): Promise<Project | null> {\n try {\n const parsed = ProjectSchema.safeParse(JSON.parse(await readFile(join(root, FILENAME), 'utf-8')));\n return parsed.success && !parsed.data.orgId ? parsed.data : null;\n } catch {\n // Pas de `project.json` lisible. L'ancien `projects.json` n'est plus\n // importé : seul le dossier de lancement le migre au démarrage.\n return null;\n }\n}\n\n/**\n * The cloud projects materialized on this machine, read from their vaults.\n *\n * The id and org come from the path, not from the file: the directory layout is\n * what the rest of the system trusts, so a file whose contents disagree with\n * where it sits must not win.\n */\nasync function readCloudProjects(): Promise<{ project: Project; root: string }[]> {\n const found: { project: Project; root: string }[] = [];\n for (const { orgId, projectId, root } of await listCloudVaults()) {\n try {\n const raw = await readFile(join(root, FILENAME), 'utf-8');\n const parsed = ProjectSchema.safeParse({ ...JSON.parse(raw), id: projectId, orgId });\n if (parsed.success) found.push({ project: parsed.data, root });\n } catch {\n // Not a readable project vault — ignore it.\n }\n }\n return found;\n}\n\n/**\n * This directory cannot hold a local project.\n *\n * Carries a stable `code` alongside the sentence: the sentence is for logs and\n * for API consumers, the code is what the UI translates — a message built here\n * could only ever be in one language. Every refusal shares this base, so the\n * caller translates them all the same way and adding one does not oblige it to\n * remember.\n */\nexport abstract class LocalProjectRefusedError extends Error {\n abstract readonly code: string;\n}\n\n/** A directory already holds its local project. */\nexport class LocalProjectExistsError extends LocalProjectRefusedError {\n readonly code = 'LOCAL_PROJECT_EXISTS';\n\n constructor(readonly projectName: string) {\n super(\n `This directory already holds the local project \"${projectName}\". ` +\n 'A directory holds one local project: use another directory, or create the project in an organization.',\n );\n this.name = 'LocalProjectExistsError';\n }\n}\n\n/** The local vault is the machine root itself: see `localVaultCollidesWithMachineRoot`. */\nexport class LocalProjectInMachineRootError extends LocalProjectRefusedError {\n readonly code = 'LOCAL_PROJECT_IN_MACHINE_ROOT';\n\n constructor() {\n super(\n 'This directory holds Runeya\\'s own data. A local project here would mix its files '\n + 'with the machine\\'s vaults: launch Runeya from another directory, or create the project in an organization.',\n );\n this.name = 'LocalProjectInMachineRootError';\n }\n}\n\n/** Ce dossier est déjà l'emplacement d'un projet d'organisation. */\nexport class LocalProjectCloudLinkedError extends LocalProjectRefusedError {\n readonly code = 'LOCAL_PROJECT_CLOUD_LINKED';\n\n constructor() {\n super('This directory is already linked to an organization project. Choose another directory.');\n this.name = 'LocalProjectCloudLinkedError';\n }\n}\n\n/** Aucun projet local à importer dans ce dossier. */\nexport class LocalProjectNotFoundError extends LocalProjectRefusedError {\n readonly code = 'LOCAL_PROJECT_NOT_FOUND';\n\n constructor() {\n super('This directory holds no local Runeya project to import.');\n this.name = 'LocalProjectNotFoundError';\n }\n}\n\nclass ProjectStore {\n /**\n * Keyed by the (organization, project) couple, never by the project id alone:\n * the same id can name a different project in each organization, and keying\n * by id let the second one read from disk overwrite the first — then `save()`\n * wrote that survivor back into both vaults.\n */\n private projects: Map<string, Project> = new Map();\n /**\n * Les coffres où un projet local a été lu ou écrit. À l'écriture, un coffre\n * de cette liste qui n'a plus de projet perd sa fiche : c'est ainsi qu'une\n * suppression ou une publication vide le dossier qu'elle quitte.\n */\n private localRoots = new Set<string>();\n private loaded = false;\n /**\n * L'état du disque tel qu'il était au dernier chargement.\n *\n * Plusieurs Runeya partagent la racine machine — l'application installée et\n * celle qu'on développe, couramment — et créent donc des projets dans les\n * mêmes coffres. Un store chargé une fois pour toutes ne voit jamais le\n * travail de l'autre : il faudrait le redémarrer pour qu'un projet créé à\n * côté existe pour lui.\n */\n private fingerprint = '';\n private readonly disk = new StoreDiskCoordinator();\n /** Emits `'saved'` after every persisted mutation, for the cloud sync bridge. */\n readonly events = new EventEmitter();\n\n private getFilePath(root: string = localVaultRoot()): string {\n return join(root, FILENAME);\n }\n\n /** Adopt a project read from disk, remembering which vault it came from. */\n private async absorb(into: Map<string, Project>, p: Project, root: string): Promise<void> {\n const projectEnvs = await environmentStore.getByProjectId(p.id).catch(() => []);\n const defaultEnv = projectEnvs.find(e => e.default);\n into.set(projectKey(p), {\n ...p,\n environmentIds: p.environmentIds ?? [],\n groupOrder: p.groupOrder ?? [],\n // Prefer the env marked default, fall back to first env\n activeEnvironmentId: defaultEnv?.id ?? p.environmentIds?.[0],\n });\n if (!p.orgId) {\n setLocalProjectRoot(p.id, root);\n this.localRoots.add(root);\n }\n setProjectVault(p, root);\n setProjectServices(p, p.serviceIds ?? []);\n }\n\n /**\n * Ce que le disque porte : quels coffres, et quand leur fiche a changé.\n *\n * Les coffres sont demandés à `listCloudVaults`, qui les découvre déjà par\n * balayage — en refaire un ici donnerait deux inventaires à tenir d'accord.\n * Seules les dates sont relevées : lire et analyser les fiches, puis les\n * absorber (ce qui va interroger les environnements), est justement ce que ce\n * repère évite de refaire à chaque appel.\n */\n private async diskFingerprint(): Promise<string> {\n return fingerprintFiles([\n this.getFilePath(),\n ...(await readLocalProjectDirs()).map((dir) => this.getFilePath(localProjectVaultRoot(dir))),\n ...(await listCloudVaults()).map((v) => this.getFilePath(v.root)),\n ]);\n }\n\n /**\n * Read every vault: the launch directory, then each organization's projects.\n *\n * Cloud vaults are discovered by walking `~/.runeya/orgs/*&#47;projects/*` rather\n * than from an index. An index is one more thing that can disagree with the\n * disk — and a project present on disk but missing from it is exactly the\n * \"already here, still shown as downloadable\" bug.\n *\n * Relu quand le disque a bougé, pas une fois pour toutes : une autre instance\n * partageant cette racine machine y crée des projets, et ne jamais relire\n * reviendrait à exiger un redémarrage pour les voir.\n */\n private async load(): Promise<void> {\n let migrated = false;\n await this.disk.load(async () => {\n const fingerprint = await this.diskFingerprint();\n if (this.loaded && fingerprint === this.fingerprint) return null;\n const next = await this.readVaults();\n return () => {\n // Ce que la session tient et que le disque ignore : l'environnement\n // actif est un choix d'affichage, jamais persisté. Un rechargement le\n // rendrait sinon au défaut du projet, sous les yeux de l'utilisateur.\n for (const [key, project] of next.projects) {\n const before = this.projects.get(key)?.activeEnvironmentId;\n if (before && project.environmentIds.includes(before)) {\n project.activeEnvironmentId = before;\n } else {\n this.unsettled.add(key);\n }\n }\n this.projects = next.projects;\n this.fingerprint = fingerprint;\n this.loaded = true;\n migrated = next.migrated;\n };\n });\n\n // Carry the launch directory over in one go rather than on the next edit:\n // a cloud project half-moved — its record in the vault, its services still\n // in `.runeya` — is the confusing state this migration exists to avoid.\n if (migrated) {\n await this.save();\n await serviceStore.relocateAll();\n await environmentStore.relocateAll();\n }\n }\n\n /** Le parcours proprement dit, dans une table neuve que `load` substitue. */\n private async readVaults(): Promise<{ projects: Map<string, Project>; migrated: boolean }> {\n const projects = new Map<string, Project>();\n this.localRoots = new Set();\n const { local, strayCloud } = await readLocalProject();\n if (local) {\n await this.absorb(projects, local, localVaultRoot());\n // Le projet du dossier de lancement rejoint la liste de la machine, une\n // fois : les autres instances le verront, d'où qu'elles soient lancées.\n if (!localVaultCollidesWithMachineRoot() && isRegistrable(localVaultRoot())) {\n await registerLocalProjectDir(dirname(localVaultRoot())).catch(() => {});\n }\n }\n for (const dir of await readLocalProjectDirs()) {\n const root = localProjectVaultRoot(dir);\n if (root === localVaultRoot()) continue;\n const found = await readLocalProjectAt(root);\n // Le même projet dans deux dossiers (une copie du répertoire) : le premier\n // lu garde sa place, l'autre n'est pas chargé.\n if (!found || projects.has(projectKey(found))) continue;\n await this.absorb(projects, found, root);\n }\n // Cloud projects an older version kept in the launch directory. Absorbed\n // first so that a vault holding the same project — necessarily fresher —\n // overrides them just below.\n let migrated = false;\n for (const project of strayCloud) {\n try {\n await this.absorb(projects, project, vaultFor(project).root);\n migrated = true;\n } catch {\n // An id that cannot form a vault path: leave it in the legacy file.\n }\n }\n for (const { project, root } of await readCloudProjects()) {\n // A cloud project left over in the local vault by an older version is\n // superseded by the copy in its own vault.\n await this.absorb(projects, project, root);\n }\n return { projects, migrated };\n }\n\n /**\n * Persist every vault this store knows about.\n *\n * Writing all of them rather than only the one that changed keeps a project\n * that moved between vaults — a local project adopted by an org, a cloud\n * project pulled for the first time — from lingering in the vault it left.\n */\n private save(): Promise<void> {\n return this.disk.save(() => this.writeVaults());\n }\n\n private async writeVaults(): Promise<void> {\n const locals: Project[] = [];\n const cloud: { project: Project; root: string }[] = [];\n for (const project of this.projects.values()) {\n if (!project.orgId) locals.push(project);\n else cloud.push({ project, root: vaultFor(project).root });\n }\n\n // Chaque projet local s'écrit dans le coffre de son dossier. Deux projets\n // sur un même coffre s'écraseraient : on refuse plutôt que d'en perdre un.\n const written = new Map<string, Project>();\n for (const local of locals) {\n const root = vaultFor(local).root;\n const other = written.get(root);\n if (other) {\n throw new Error(\n `Refusing to save two local projects in ${root}: \"${other.name}\" and \"${local.name}\". `\n + 'A directory holds a single local project.',\n );\n }\n written.set(root, local);\n }\n for (const [root, local] of written) {\n // Écrire un projet local, c'est ce qui fait naître son `.runeya`.\n await mkdir(root, { recursive: true });\n await ensureDataGitignore(root);\n await atomicWriteSecure(this.getFilePath(root), JSON.stringify(toDiskForm(local), null, 2));\n this.localRoots.add(root);\n }\n // Un coffre qui n'a plus de projet perd sa fiche, et son dossier quitte la\n // liste de la machine.\n for (const root of this.localRoots) {\n if (written.has(root)) continue;\n await rm(this.getFilePath(root), { force: true }).catch(() => {});\n if (isRegistrable(root)) await unregisterLocalProjectDir(dirname(root)).catch(() => {});\n this.localRoots.delete(root);\n }\n // The legacy list has been carried over — park it rather than delete it.\n await rename(join(localVaultRoot(), LEGACY_FILENAME), join(localVaultRoot(), LEGACY_FILENAME + '.bak'))\n .catch(() => {});\n\n for (const { project, root } of cloud) {\n await atomicWriteSecure(this.getFilePath(root), JSON.stringify(toDiskForm(project), null, 2));\n setProjectVault(project, root);\n }\n\n // Nos propres écritures deviennent la référence : sans ça, le chargement\n // suivant les prendrait pour celles d'une autre instance et relirait tout.\n this.fingerprint = await this.diskFingerprint();\n\n this.events.emit('saved');\n }\n\n /**\n * The project a caller means, from a couple or from a bare id.\n *\n * A ref carrying its `orgId` — even an explicit `null` for a local project —\n * names exactly one project and is looked up directly. A bare id is what a\n * contained entity (an environment, a conversation) still carries, and it is\n * resolved only while it answers for a single project: electing one of two\n * would write into the wrong organization's vault.\n */\n private resolveKey(target: ProjectRef | string): string | null {\n if (typeof target !== 'string' && target.orgId !== undefined) return projectKey(target);\n const id = typeof target === 'string' ? target : target.id;\n const matches = Array.from(this.projects.values()).filter((p) => p.id === id);\n if (matches.length > 1) {\n throw new AmbiguousProjectError(id, matches.map((p) => p.orgId ?? null));\n }\n return matches[0] ? projectKey(matches[0]) : null;\n }\n\n private find(target: ProjectRef | string): { key: string; project: Project } | null {\n const key = this.resolveKey(target);\n const project = key ? this.projects.get(key) : undefined;\n return key && project ? { key, project } : null;\n }\n\n /**\n * Le projet déjà placé dans ce dossier, parmi ceux que ce poste connaît.\n *\n * Une entrée de `project-paths.json` peut survivre à son projet (supprimé\n * ailleurs, ou avant que la suppression ne la nettoie). Elle ne réserve plus\n * rien : le refus qui en découlait nommait un projet introuvable.\n */\n async projectAtPath(path: string, exclude?: ProjectRef): Promise<string | null> {\n await this.load();\n const ids = new Set(Array.from(this.projects.values(), (p) => p.id));\n // Clé en couple, ou id nu pour une entrée écrite par une ancienne version.\n return projectPathStore.projectAt(path, exclude, (key) => this.projects.has(key) || ids.has(key));\n }\n\n async list(): Promise<Project[]> {\n await this.load();\n await this.settleActiveEnvironments();\n return Array.from(this.projects.values());\n }\n\n async get(target: ProjectRef | string): Promise<Project | null> {\n await this.load();\n await this.settleActiveEnvironments();\n return this.find(target)?.project ?? null;\n }\n\n /**\n * Projets dont l'environnement actif vient d'un repli et n'a pas été vérifié.\n *\n * Au chargement, les environnements ne sont pas encore lisibles : leur magasin\n * attend justement la fin de ce chargement. `absorb` retient donc le premier\n * id de `environmentIds`, qui peut désigner un environnement disparu. Le\n * sélecteur affichait alors cet id brut, absent de sa liste.\n */\n private readonly unsettled = new Set<string>();\n private settling = false;\n\n /** Retenir, pour chaque projet non vérifié, un environnement qui existe. */\n private async settleActiveEnvironments(): Promise<void> {\n if (this.settling || this.unsettled.size === 0) return;\n this.settling = true;\n try {\n for (const key of this.unsettled) {\n const project = this.projects.get(key);\n if (!project) {\n this.unsettled.delete(key);\n continue;\n }\n const envs = (await environmentStore.getByProjectId(project.id, project.orgId ?? null).catch(() => []))\n .filter((e) => e.scope !== 'service');\n // Rien de lu : coffre verrouillé ou projet sans environnement. On\n // réessaiera à la prochaine lecture plutôt que d'effacer le repli.\n if (envs.length === 0) continue;\n const current = envs.find((e) => e.id === project.activeEnvironmentId);\n const chosen = envs.find((e) => e.default) ?? current ?? envs[0];\n project.activeEnvironmentId = chosen.id;\n this.unsettled.delete(key);\n }\n } finally {\n this.settling = false;\n }\n }\n\n /**\n * Vérifier qu'un dossier peut accueillir un projet local, et rendre son coffre.\n *\n * Refusé si son `.runeya` serait la racine machine, si le dossier est déjà\n * l'emplacement d'un projet d'organisation, ou s'il porte déjà un projet.\n */\n private async localRootFor(root: string): Promise<string> {\n if (collidesWithMachineRoot(root)) throw new LocalProjectInMachineRootError();\n if (await this.projectAtPath(dirname(root))) throw new LocalProjectCloudLinkedError();\n const known = Array.from(this.projects.values()).find((p) => !p.orgId && vaultFor(p).root === root);\n if (known) throw new LocalProjectExistsError(known.name);\n const onDisk = await readLocalProjectAt(root);\n if (onDisk) throw new LocalProjectExistsError(onDisk.name);\n if (root !== localVaultRoot() && await holdsRuneyaProject(dirname(root))) throw new LocalProjectExistsError('');\n return root;\n }\n\n /**\n * `localDir` : le dossier d'un projet local. Omis, c'est le dossier de\n * lancement, comme avant que les projets locaux aient chacun le leur.\n */\n async create(input: CreateProject, localDir?: string): Promise<Project> {\n await this.load();\n const localRoot = input.orgId\n ? null\n : await this.localRootFor(localDir ? localProjectVaultRoot(localDir) : localVaultRoot());\n const project: Project = {\n id: randomUUID(),\n orgId: input.orgId ?? null,\n name: input.name,\n description: input.description ?? '',\n serviceIds: input.serviceIds ?? [],\n environmentIds: input.environmentIds ?? [],\n activeEnvironmentId: input.activeEnvironmentId,\n groupOrder: input.groupOrder ?? [],\n };\n this.projects.set(projectKey(project), project);\n if (localRoot) {\n setLocalProjectRoot(project.id, localRoot);\n if (isRegistrable(localRoot)) await registerLocalProjectDir(dirname(localRoot));\n }\n setProjectVault(project, vaultFor(project).root);\n setProjectServices(project, project.serviceIds);\n await this.save();\n return project;\n }\n\n /**\n * Ce qu'un dossier permet pour un projet local : en créer un (`available`),\n * importer celui qu'il porte (`importable`), rien parce qu'il est déjà dans\n * la liste (`listed`), ou rien parce qu'il est refusé.\n */\n async inspectLocalDir(dir: string): Promise<{\n status: 'available' | 'importable' | 'listed' | 'cloud-linked' | 'machine-root' | 'unreadable';\n projectName: string | null;\n }> {\n await this.load();\n const root = localProjectVaultRoot(dir);\n if (collidesWithMachineRoot(root)) return { status: 'machine-root', projectName: null };\n if (await this.projectAtPath(resolve(dir))) return { status: 'cloud-linked', projectName: null };\n const known = Array.from(this.projects.values()).find((p) => !p.orgId && vaultFor(p).root === root);\n if (known) return { status: 'listed', projectName: known.name };\n const onDisk = await readLocalProjectAt(root);\n if (onDisk) return { status: 'importable', projectName: onDisk.name };\n // Un fichier de projet qu'on ne sait pas importer : on n'en crée pas un\n // second par-dessus.\n if (await holdsRuneyaProject(dirname(root))) return { status: 'unreadable', projectName: null };\n return { status: 'available', projectName: null };\n }\n\n /**\n * Ajouter à la liste le projet local déjà présent dans ce dossier.\n *\n * Son `.runeya` n'est pas touché : le dossier est seulement enregistré, et le\n * projet relu comme les autres.\n */\n async importLocal(dir: string): Promise<Project> {\n await this.load();\n const root = localProjectVaultRoot(dir);\n if (collidesWithMachineRoot(root)) throw new LocalProjectInMachineRootError();\n if (await this.projectAtPath(resolve(dir))) throw new LocalProjectCloudLinkedError();\n const found = await readLocalProjectAt(root);\n if (!found) throw new LocalProjectNotFoundError();\n const known = this.projects.get(projectKey(found));\n if (known) return known;\n await registerLocalProjectDir(dirname(root));\n this.loaded = false;\n await this.load();\n return this.projects.get(projectKey(found)) ?? found;\n }\n\n /**\n * `input.orgId` names which project is being updated, it does not move it:\n * a project is born local or in an organization and stays there, so treating\n * it as a new value would silently relocate a vault instead of editing it.\n */\n async update(input: UpdateProject): Promise<Project | null> {\n await this.load();\n const found = this.find(input);\n if (!found) return null;\n const { key, project: existing } = found;\n const updated: Project = {\n ...existing,\n ...(input.name !== undefined && { name: input.name }),\n ...(input.description !== undefined && { description: input.description }),\n ...(input.serviceIds !== undefined && { serviceIds: input.serviceIds }),\n ...(input.environmentIds !== undefined && { environmentIds: input.environmentIds }),\n ...(input.activeEnvironmentId !== undefined && { activeEnvironmentId: input.activeEnvironmentId }),\n ...(input.groupOrder !== undefined && { groupOrder: input.groupOrder }),\n };\n recordDetached(existing.id, 'serviceIds', existing.serviceIds ?? [], updated.serviceIds ?? []);\n recordDetached(existing.id, 'environmentIds', existing.environmentIds ?? [], updated.environmentIds ?? []);\n this.projects.set(key, updated);\n setProjectVault(updated, vaultFor(updated).root);\n setProjectServices(updated, updated.serviceIds);\n await this.save();\n return updated;\n }\n\n /**\n * Faire passer le projet local de ce dossier dans une organisation.\n *\n * Le seul déplacement de coffre qu'on s'autorise, et il ne va que dans ce\n * sens : le projet quitte `<cwd>/.runeya` pour\n * `~/.runeya/orgs/<org>/projects/<id>/`, en gardant son identifiant. Il n'y a\n * donc jamais deux copies à réconcilier — c'est ce qui rendait « publier »\n * dangereux tant que le projet restait aussi local.\n *\n * Refusé si l'organisation porte déjà ce projet : la copie d'ici écraserait\n * celle du cloud, et ces deux histoires ne sont pas à nous de les fusionner.\n *\n * Ce qui suit le projet est écrit par les stores eux-mêmes : `save()` réécrit\n * chaque coffre — le fichier local disparaît, celui de l'org apparaît — puis\n * les contenus sont relogés depuis le registre, qui pointe désormais ailleurs.\n */\n async moveToOrg(projectId: string, orgId: string): Promise<Project> {\n await this.load();\n const local = this.projects.get(projectKey({ id: projectId, orgId: null }));\n if (!local) throw new Error('PROJECT_NOT_LOCAL');\n if (this.projects.get(projectKey({ id: projectId, orgId }))) {\n throw new Error('PROJECT_ALREADY_IN_ORG');\n }\n\n // Un projet local est situé par son `.runeya`, pas par le store des\n // chemins. Publié, il ne l'est plus que par ce store : son dossier y est\n // donc inscrit avant que la racine locale soit oubliée. Le projet du\n // dossier de lancement n'en a pas à lui (cf. `projectRoot`).\n const localRoot = vaultFor(local).root;\n const localDir = localRoot === localVaultRoot() ? null : dirname(localRoot);\n\n const moved: Project = { ...local, orgId };\n this.projects.delete(projectKey(local));\n forgetProject(local);\n forgetLocalProjectRoot(local.id);\n this.projects.set(projectKey(moved), moved);\n setProjectVault(moved, vaultFor(moved).root);\n setProjectServices(moved, moved.serviceIds ?? []);\n await this.save();\n if (localDir && !(await projectPathStore.get(moved))) {\n await projectPathStore.set(moved, localDir);\n }\n\n // Les services nomment leur organisation ; les environnements et les\n // conversations, non — eux suivent le coffre de leur projet.\n await serviceStore.reassignOrg(moved.serviceIds ?? [], orgId);\n await environmentStore.relocateAll();\n return moved;\n }\n\n async delete(target: ProjectRef | string): Promise<boolean> {\n await this.load();\n const found = this.find(target);\n if (!found) return false;\n const { key, project } = found;\n this.projects.delete(key);\n await this.save();\n await this.discardVault(project);\n // Le répertoire cesse d'être réservé. `projectAt` s'en sert pour interdire\n // que deux projets partagent un dossier : une entrée laissée derrière un\n // projet supprimé continuait de le retenir, et le refus qui en découlait\n // nommait un projet qui n'existe plus.\n await projectPathStore.clear(project);\n // Les sections de conversations ne valent que pour ce projet.\n await conversationSectionStore.forgetProject(project.id);\n return true;\n }\n\n /**\n * Remove what a deleted cloud project left behind.\n *\n * Its vault is a directory of its own, so dropping the project means dropping\n * the directory — otherwise `readCloudVaults` would resurrect it on the next\n * load. Local projects share their vault with the directory's own files and\n * are only removed from the list.\n */\n private async discardVault(project: Project): Promise<void> {\n forgetProject(project);\n if (!project.orgId) forgetLocalProjectRoot(project.id);\n if (!project.orgId) return;\n await rm(vaultFor(project).root, { recursive: true, force: true }).catch(() => {});\n }\n\n /**\n * Stop holding a project on this machine, leaving the doc alone.\n *\n * Memory only: the caller removes the vault itself. Its contents are dropped\n * too, because a service or environment surviving in memory would be written\n * back on the next save — and, its project being unknown by then, into the\n * local vault.\n */\n async forgetLocally(target: ProjectRef | string): Promise<void> {\n await this.load();\n const found = this.find(target);\n if (!found) return;\n const { key, project } = found;\n this.projects.delete(key);\n forgetProject(project);\n await serviceStore.forgetLocally(project.serviceIds ?? []);\n await environmentStore.forgetByProject(project.id);\n // Ce poste ne tient plus ce projet — son coffre vient d'être effacé — donc\n // il ne réserve plus de répertoire non plus. Le seul appelant est le\n // retrait volontaire d'un projet suivi, jamais un état transitoire.\n await projectPathStore.clear(project);\n }\n\n /**\n * Le projet qui porte un service, ou `null` s'il n'est rattaché à aucun.\n *\n * Un service n'appartient qu'à un projet : la recherche s'arrête au premier.\n */\n async ofService(serviceId: string): Promise<Project | null> {\n await this.load();\n for (const project of this.projects.values()) {\n if (project.serviceIds.includes(serviceId)) return project;\n }\n return null;\n }\n\n /** All projects linked to a given organization (for the sync bridge). */\n async listByOrg(orgId: string): Promise<Project[]> {\n await this.load();\n return Array.from(this.projects.values()).filter((p) => p.orgId === orgId);\n }\n\n /** Projects of an organization in their on-disk form (what the cloud mirrors). */\n async listDiskFormByOrg(orgId: string): Promise<Omit<Project, 'activeEnvironmentId'>[]> {\n return (await this.listByOrg(orgId)).map(toDiskForm);\n }\n\n /**\n * Apply a project received from the cloud, in its on-disk form.\n *\n * Validated rather than trusted: a peer on another version — or a tampered\n * doc — must not be able to write arbitrary shapes into `projects.json`.\n * `activeEnvironmentId` is runtime-only and stays whatever it is locally.\n * Diff-guarded: an identical project neither saves nor emits (loop-safe).\n * Returns whether a new local project was created, so the caller can\n * bootstrap project-local state (e.g. a default environment).\n */\n async applyRemoteUpsert(\n id: string,\n fields: unknown,\n ): Promise<{ project: Project; created: boolean } | null> {\n await this.load();\n const parsed = ProjectSchema.safeParse({ ...(fields as object), id });\n if (!parsed.success) {\n console.error('[project-store] Rejected remote project %s:', id, parsed.error.message);\n return null;\n }\n const incoming = toDiskForm(parsed.data);\n // The couple, taken from the validated project itself: the bridge stamps\n // the org of the connection that carried it, so this key names the vault\n // the doc actually belongs to.\n const key = projectKey(parsed.data);\n const existing = this.projects.get(key);\n if (existing) {\n if (JSON.stringify(toDiskForm(existing)) === JSON.stringify(incoming)) {\n return { project: existing, created: false };\n }\n const updated: Project = { ...incoming, activeEnvironmentId: existing.activeEnvironmentId };\n this.projects.set(key, updated);\n setProjectVault(updated, vaultFor(updated).root);\n setProjectServices(updated, updated.serviceIds);\n await this.save();\n return { project: updated, created: false };\n }\n const project: Project = { ...incoming, activeEnvironmentId: undefined };\n this.projects.set(key, project);\n setProjectVault(project, vaultFor(project).root);\n setProjectServices(project, project.serviceIds);\n await this.save();\n return { project, created: true };\n }\n\n /** Delete a project that was removed on another peer. */\n async applyRemoteDelete(target: ProjectRef | string): Promise<boolean> {\n await this.load();\n const found = this.find(target);\n if (!found) return false;\n const { key, project } = found;\n this.projects.delete(key);\n await this.save();\n await this.discardVault(project);\n // Même raison que dans `delete` : sans cela, le dossier restait réservé\n // par un projet que ce poste ne connaît plus.\n await projectPathStore.clear(project);\n return true;\n }\n\n async addService(target: ProjectRef | string, serviceId: string): Promise<Project | null> {\n await this.load();\n const found = this.find(target);\n if (!found) return null;\n const { key, project } = found;\n if (project.serviceIds.includes(serviceId)) return project;\n const updated = { ...project, serviceIds: [...project.serviceIds, serviceId] };\n this.projects.set(key, updated);\n setProjectServices(updated, updated.serviceIds);\n await this.save();\n return updated;\n }\n\n async removeService(target: ProjectRef | string, serviceId: string): Promise<Project | null> {\n await this.load();\n const found = this.find(target);\n if (!found) return null;\n const { key, project } = found;\n const updated = { ...project, serviceIds: project.serviceIds.filter(id => id !== serviceId) };\n recordDetached(project.id, 'serviceIds', project.serviceIds, updated.serviceIds);\n this.projects.set(key, updated);\n setProjectServices(updated, updated.serviceIds);\n await this.save();\n return updated;\n }\n\n async addEnvironment(target: ProjectRef | string, environmentId: string): Promise<Project | null> {\n await this.load();\n const found = this.find(target);\n if (!found) return null;\n const { key, project } = found;\n if (project.environmentIds?.includes(environmentId)) return project;\n const updated = {\n ...project,\n environmentIds: [...(project.environmentIds ?? []), environmentId],\n };\n this.projects.set(key, updated);\n await this.save();\n return updated;\n }\n\n async removeEnvironment(target: ProjectRef | string, environmentId: string): Promise<Project | null> {\n await this.load();\n const found = this.find(target);\n if (!found) return null;\n const { key, project } = found;\n const updated = {\n ...project,\n environmentIds: (project.environmentIds ?? []).filter(id => id !== environmentId),\n // Clear activeEnvironmentId if it was the removed environment\n ...(project.activeEnvironmentId === environmentId && { activeEnvironmentId: undefined }),\n };\n recordDetached(project.id, 'environmentIds', project.environmentIds ?? [], updated.environmentIds);\n this.projects.set(key, updated);\n await this.save();\n return updated;\n }\n\n async setActiveEnvironment(target: ProjectRef | string, environmentId: string): Promise<Project | null> {\n await this.load();\n const found = this.find(target);\n if (!found) return null;\n const { key, project } = found;\n\n // Update activeEnvironmentId in memory only (not persisted to JSON)\n // Do NOT update updatedAt since this is a runtime preference, not a project modification\n const updated: Project = {\n ...project,\n activeEnvironmentId: environmentId,\n };\n\n this.projects.set(key, updated);\n // Un choix de l'utilisateur : la vérification du repli ne doit plus l'écraser.\n this.unsettled.delete(key);\n // No save() call - activeEnvironmentId is runtime-only\n return updated;\n }\n}\n\nexport const projectStore = new ProjectStore();\n\n// Content stores resolve an entity's vault through the registry, which only\n// knows what the projects say — so it must be able to make them load first.\nregisterProjectLoader(() => projectStore.list().then(() => undefined));\n","import { writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nconst GITIGNORE_CONTENT = `\n#################\n# Do not edit this file #\n#################\n\n\n# Encryption key (contains secrets — never commit)\n.encryption.key\n.encryption.key.enc\n\n\n# Key verification canary — MUST be committed so new team members can validate their encryption key\n!.runeya-verify\n\n# This machine's cloud session token (encrypted, and specific to this install)\ncloud-session.json\n\n# Backup in local only\nbackup-*\n\n# Per-user environment overrides (never commit — each dev has their own)\nenvironment-overrides.json\n\n# Private user data (conversations, personal state — never commit)\n*.private\n*.private.json*\nconversations/\nconversations.private.json\n\n\n\n# Runtime logs\n*.log\n*.log.*\n`;\n\n/**\n * Writes data/.gitignore on every startup to keep it in sync with the codebase.\n */\nexport async function ensureDataGitignore(dataDir: string): Promise<void> {\n const gitignorePath = join(dataDir, '.gitignore');\n await writeFile(gitignorePath, GITIGNORE_CONTENT, 'utf-8');\n}\n","import { randomUUID } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport { machineFilePath } from './vault-resolver.js';\nimport { atomicWriteSecure } from './run-store-utils.js';\n\nconst FILENAME = 'conversation-sections.json';\n\n/**\n * Une section de la barre latérale des conversations IA, créée par\n * l'utilisateur pour y ranger ses conversations à la main.\n *\n * Propre à un projet : ses conversations ne se voient que dans ce projet.\n * La section « Épinglé » n'en fait pas partie, elle reste portée par\n * `Conversation.pinOrder`.\n */\nexport interface ConversationSection {\n id: string;\n name: string;\n /** Rang parmi les sections du projet. Seul l'ordre relatif compte. */\n order: number;\n}\n\n/** `projectId` → sections du projet. */\ntype SectionMap = Record<string, ConversationSection[]>;\n\nfunction isSectionMap(value: unknown): value is SectionMap {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n return Object.values(value).every(list => Array.isArray(list) && list.every(s =>\n !!s && typeof s === 'object'\n && typeof (s as ConversationSection).id === 'string'\n && typeof (s as ConversationSection).name === 'string'\n && typeof (s as ConversationSection).order === 'number'));\n}\n\n/**\n * Les sections, dans un fichier de la racine machine.\n *\n * Le fichier est relu avant chaque lecture et chaque écriture : deux Runeya du\n * même poste le partagent, et une copie gardée en mémoire effacerait à la\n * sauvegarde ce que l'autre vient d'écrire. Il reste petit, la relecture ne\n * coûte rien.\n */\nclass ConversationSectionStore {\n private async read(): Promise<SectionMap> {\n try {\n const parsed = JSON.parse(await readFile(machineFilePath(FILENAME), 'utf-8')) as unknown;\n return isSectionMap(parsed) ? parsed : {};\n } catch {\n return {};\n }\n }\n\n private async write(map: SectionMap): Promise<void> {\n await atomicWriteSecure(machineFilePath(FILENAME), JSON.stringify(map, null, 2));\n }\n\n /** Les sections d'un projet, dans l'ordre choisi. */\n async list(projectId: string): Promise<ConversationSection[]> {\n const map = await this.read();\n return (map[projectId] ?? []).slice().sort((a, b) => a.order - b.order);\n }\n\n async get(projectId: string, id: string): Promise<ConversationSection | null> {\n return (await this.list(projectId)).find(s => s.id === id) ?? null;\n }\n\n /** Crée une section, posée après les autres. */\n async create(projectId: string, name: string): Promise<ConversationSection> {\n const map = await this.read();\n const list = map[projectId] ?? [];\n const order = list.length > 0 ? Math.max(...list.map(s => s.order)) + 1 : 0;\n const section: ConversationSection = { id: randomUUID(), name, order };\n map[projectId] = [...list, section];\n await this.write(map);\n return section;\n }\n\n async rename(projectId: string, id: string, name: string): Promise<ConversationSection | null> {\n const map = await this.read();\n const section = map[projectId]?.find(s => s.id === id);\n if (!section) return null;\n section.name = name;\n await this.write(map);\n return section;\n }\n\n /** Retire une section. Rend `false` si elle n'existait pas. */\n async remove(projectId: string, id: string): Promise<boolean> {\n const map = await this.read();\n const list = map[projectId] ?? [];\n const next = list.filter(s => s.id !== id);\n if (next.length === list.length) return false;\n if (next.length > 0) map[projectId] = next;\n else delete map[projectId];\n await this.write(map);\n return true;\n }\n\n /**\n * Range les sections du projet dans l'ordre de `ids`.\n *\n * Une section absente de `ids` passe après les autres, dans son ordre\n * d'avant : un appel parti avant la création d'une section ne la fait pas\n * disparaître. Un id inconnu est ignoré.\n */\n async reorder(projectId: string, ids: string[]): Promise<void> {\n const map = await this.read();\n const list = (map[projectId] ?? []).slice().sort((a, b) => a.order - b.order);\n if (list.length === 0) return;\n const rank = new Map(Array.from(new Set(ids)).map((id, i) => [id, i]));\n const listed = list.filter(s => rank.has(s.id)).sort((a, b) => rank.get(a.id)! - rank.get(b.id)!);\n const others = list.filter(s => !rank.has(s.id));\n map[projectId] = [...listed, ...others].map((s, i) => ({ ...s, order: i }));\n await this.write(map);\n }\n\n /** Oublie les sections d'un projet supprimé. */\n async forgetProject(projectId: string): Promise<void> {\n const map = await this.read();\n if (!(projectId in map)) return;\n delete map[projectId];\n await this.write(map);\n }\n}\n\nexport const conversationSectionStore = new ConversationSectionStore();\n","import { readFile, writeFile, rename, mkdir, chmod, stat } from 'node:fs/promises';\nimport { isAbsolute, join, resolve } from 'node:path';\nimport { machineFilePath, machineRoot } from './vault-resolver.js';\nimport { projectKey, type ProjectRef } from './project-key.js';\n\nconst FILENAME = 'project-paths.json';\n\n/**\n * Where each cloud project lives on THIS machine.\n *\n * A cloud project is materialized from its document, which holds no path — and\n * must not: a directory of mine means nothing on a teammate's machine, and the\n * sync pushes `projects`, `services` and `environments` wholesale, so a path\n * added to any of them would travel. Hence a file of its own, machine-local and\n * outside every synced collection.\n *\n * Without it, a service whose `cwd` is relative falls back to `'.'` — the\n * directory Runeya was launched from. Every cloud project opened from the same\n * place would then run in the same directory, which is precisely what this is\n * here to prevent.\n */\n\n/**\n * `${orgId ?? 'local'}:${projectId}` → absolute directory on this machine.\n *\n * Keyed by the couple, because a project id alone can name a different project\n * in each organization — two of them would then share one directory, which is\n * exactly what this file exists to prevent. Entries written by older versions\n * are keyed by the bare id; they are still read, and rewritten under the couple\n * the next time the project is placed.\n */\ntype PathMap = Record<string, string>;\n\nclass ProjectPathStore {\n private paths: PathMap | null = null;\n\n private async load(): Promise<PathMap> {\n if (this.paths) return this.paths;\n try {\n const raw = await readFile(machineFilePath(FILENAME), 'utf-8');\n const parsed = JSON.parse(raw) as unknown;\n this.paths = isPathMap(parsed) ? parsed : {};\n } catch {\n // Absent or unreadable: no project has been placed yet.\n this.paths = {};\n }\n return this.paths;\n }\n\n private async persist(): Promise<void> {\n await mkdir(machineRoot(), { recursive: true });\n const filePath = machineFilePath(FILENAME);\n const tmpPath = `${filePath}.tmp`;\n await writeFile(tmpPath, JSON.stringify(this.paths ?? {}, null, 2), 'utf-8');\n await rename(tmpPath, filePath);\n await chmod(filePath, 0o600);\n }\n\n /** The directory chosen for this project, or null while it has none. */\n async get(ref: ProjectRef): Promise<string | null> {\n const paths = await this.load();\n return paths[projectKey(ref)] ?? paths[ref.id] ?? null;\n }\n\n async all(): Promise<PathMap> {\n return { ...(await this.load()) };\n }\n\n /**\n * The project already placed in this directory, if any other one is.\n *\n * Answers with the key as stored — the couple, or the bare id for an entry an\n * older version wrote — which is enough to say \"taken\" and to name it.\n *\n * `isKnown` écarte les entrées d'un projet que ce poste ne connaît plus : un\n * projet disparu ne doit pas continuer de retenir son dossier.\n */\n async projectAt(\n path: string,\n exclude?: ProjectRef,\n isKnown: (key: string) => boolean = () => true,\n ): Promise<string | null> {\n const target = resolve(path);\n const excluded = exclude ? [projectKey(exclude), exclude.id] : [];\n for (const [key, dir] of Object.entries(await this.load())) {\n if (!excluded.includes(key) && resolve(dir) === target && isKnown(key)) return key;\n }\n return null;\n }\n\n async set(ref: ProjectRef, path: string): Promise<void> {\n if (!isAbsolute(path)) throw new Error('Le chemin du projet doit être absolu');\n const paths = await this.load();\n // The legacy entry goes with it: leaving it behind would keep answering for\n // a project that has since been placed somewhere else.\n delete paths[ref.id];\n paths[projectKey(ref)] = resolve(path);\n await this.persist();\n }\n\n /**\n * Faire suivre le répertoire quand un projet change d'organisation.\n *\n * La clé est le couple `(organisation, projet)` : publier un projet local le\n * fait passer de `local:<id>` à `<org>:<id>`, et l'entrée d'avant cesse alors\n * de répondre — `get` ne retombe que sur l'identifiant nu, forme héritée, pas\n * sur `local:<id>`. Le projet se retrouvait donc sans emplacement connu juste\n * après avoir été publié, alors que ses sources n'avaient pas bougé d'un\n * octet.\n */\n async relocate(from: ProjectRef, to: ProjectRef): Promise<void> {\n const paths = await this.load();\n const previous = paths[projectKey(from)] ?? paths[from.id];\n if (!previous) return;\n delete paths[projectKey(from)];\n delete paths[from.id];\n paths[projectKey(to)] = previous;\n await this.persist();\n }\n\n async clear(ref: ProjectRef): Promise<void> {\n const paths = await this.load();\n const keys = [projectKey(ref), ref.id].filter((k) => k in paths);\n if (keys.length === 0) return;\n for (const key of keys) delete paths[key];\n await this.persist();\n }\n\n /** Test seam: forget what was read from disk. */\n reset(): void {\n this.paths = null;\n }\n}\n\nfunction isPathMap(value: unknown): value is PathMap {\n return (\n !!value\n && typeof value === 'object'\n && !Array.isArray(value)\n && Object.values(value as Record<string, unknown>).every((v) => typeof v === 'string')\n );\n}\n\n/**\n * Whether a directory already holds a Runeya project.\n *\n * Two projects sharing a directory is the very situation that makes services\n * run against the wrong code, so it is refused rather than warned about.\n */\nexport async function holdsRuneyaProject(path: string): Promise<boolean> {\n // L'ancien `projects.json` n'est plus lu hors du dossier de lancement : il\n // ne retient donc pas le dossier.\n return stat(join(path, '.runeya', 'project.json')).then(() => true, () => false);\n}\n\nexport const projectPathStore = new ProjectPathStore();\n"],"mappings":"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","names":["randomUUID","mkdir","readFile","rename","join","resolve","readFile","writeFile","join","FILENAME","readFile","writeFile","join","FILENAME","readFile","join","resolve","mkdir","rename","randomUUID"]}
@@ -0,0 +1,43 @@
1
+ // ../server/src/services/mcp-ready.ts
2
+ var EARLY_SIGNAL_TTL_MS = 6e4;
3
+ var waiters = /* @__PURE__ */ new Map();
4
+ var earlySignals = /* @__PURE__ */ new Map();
5
+ function signalMcpReady(id) {
6
+ const waiter = waiters.get(id);
7
+ if (waiter) {
8
+ waiter();
9
+ return;
10
+ }
11
+ if (earlySignals.has(id)) return;
12
+ const timer = setTimeout(() => earlySignals.delete(id), EARLY_SIGNAL_TTL_MS);
13
+ timer.unref?.();
14
+ earlySignals.set(id, timer);
15
+ }
16
+ function waitForMcpReady(id, timeoutMs, signal) {
17
+ const early = earlySignals.get(id);
18
+ if (early) {
19
+ clearTimeout(early);
20
+ earlySignals.delete(id);
21
+ return Promise.resolve(true);
22
+ }
23
+ if (signal?.aborted) return Promise.resolve(false);
24
+ return new Promise((resolve) => {
25
+ const finish = (ready) => {
26
+ clearTimeout(timer);
27
+ signal?.removeEventListener("abort", onAbort);
28
+ if (waiters.get(id) === onReady) waiters.delete(id);
29
+ resolve(ready);
30
+ };
31
+ const onReady = () => finish(true);
32
+ const onAbort = () => finish(false);
33
+ const timer = setTimeout(() => finish(false), timeoutMs);
34
+ signal?.addEventListener("abort", onAbort, { once: true });
35
+ waiters.set(id, onReady);
36
+ });
37
+ }
38
+
39
+ export {
40
+ signalMcpReady,
41
+ waitForMcpReady
42
+ };
43
+ //# sourceMappingURL=chunk-DA2DZBTL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../server/src/services/mcp-ready.ts"],"sourcesContent":["/**\n * Attente du serveur MCP Runeya d'un terminal lancé par le serveur.\n *\n * Claude Code traite le prompt passé en argument avant d'avoir connecté ses\n * serveurs MCP : sur un tour court, les outils `mcp__runeya__*` manquent au\n * moment de répondre. Le runner PTY lance donc le terminal sans prompt, avec un\n * identifiant que l'agent transmet au serveur MCP (`RUNEYA_MCP_READY_ID`).\n * Une fois initialisé par le CLI, ce serveur appelle `mcpReady.signal` avec cet\n * identifiant, et le runner écrit alors le prompt.\n *\n * Tout vit en mémoire : un identifiant ne sert qu'à un lancement.\n */\n\n/** Durée de vie d'un signal arrivé avant que quelqu'un l'attende. */\nconst EARLY_SIGNAL_TTL_MS = 60_000;\n\nconst waiters = new Map<string, () => void>();\nconst earlySignals = new Map<string, ReturnType<typeof setTimeout>>();\n\n/** Note que le serveur MCP du lancement `id` est prêt. */\nexport function signalMcpReady(id: string): void {\n const waiter = waiters.get(id);\n if (waiter) { waiter(); return; }\n if (earlySignals.has(id)) return;\n const timer = setTimeout(() => earlySignals.delete(id), EARLY_SIGNAL_TTL_MS);\n timer.unref?.();\n earlySignals.set(id, timer);\n}\n\n/**\n * Attend le signal du lancement `id`. Rend `true` s'il arrive (ou était déjà\n * arrivé), `false` au bout de `timeoutMs` ou si `signal` est interrompu.\n *\n * Un agent trop ancien pour transmettre l'identifiant n'enverra jamais ce\n * signal : le délai couvre ce cas.\n */\nexport function waitForMcpReady(id: string, timeoutMs: number, signal?: AbortSignal): Promise<boolean> {\n const early = earlySignals.get(id);\n if (early) {\n clearTimeout(early);\n earlySignals.delete(id);\n return Promise.resolve(true);\n }\n if (signal?.aborted) return Promise.resolve(false);\n return new Promise<boolean>((resolve) => {\n const finish = (ready: boolean) => {\n clearTimeout(timer);\n signal?.removeEventListener('abort', onAbort);\n if (waiters.get(id) === onReady) waiters.delete(id);\n resolve(ready);\n };\n const onReady = () => finish(true);\n const onAbort = () => finish(false);\n const timer = setTimeout(() => finish(false), timeoutMs);\n signal?.addEventListener('abort', onAbort, { once: true });\n waiters.set(id, onReady);\n });\n}\n"],"mappings":";AAcA,IAAM,sBAAsB;AAE5B,IAAM,UAAU,oBAAI,IAAwB;AAC5C,IAAM,eAAe,oBAAI,IAA2C;AAG7D,SAAS,eAAe,IAAkB;AAC/C,QAAM,SAAS,QAAQ,IAAI,EAAE;AAC7B,MAAI,QAAQ;AAAE,WAAO;AAAG;AAAA,EAAQ;AAChC,MAAI,aAAa,IAAI,EAAE,EAAG;AAC1B,QAAM,QAAQ,WAAW,MAAM,aAAa,OAAO,EAAE,GAAG,mBAAmB;AAC3E,QAAM,QAAQ;AACd,eAAa,IAAI,IAAI,KAAK;AAC5B;AASO,SAAS,gBAAgB,IAAY,WAAmB,QAAwC;AACrG,QAAM,QAAQ,aAAa,IAAI,EAAE;AACjC,MAAI,OAAO;AACT,iBAAa,KAAK;AAClB,iBAAa,OAAO,EAAE;AACtB,WAAO,QAAQ,QAAQ,IAAI;AAAA,EAC7B;AACA,MAAI,QAAQ,QAAS,QAAO,QAAQ,QAAQ,KAAK;AACjD,SAAO,IAAI,QAAiB,CAAC,YAAY;AACvC,UAAM,SAAS,CAAC,UAAmB;AACjC,mBAAa,KAAK;AAClB,cAAQ,oBAAoB,SAAS,OAAO;AAC5C,UAAI,QAAQ,IAAI,EAAE,MAAM,QAAS,SAAQ,OAAO,EAAE;AAClD,cAAQ,KAAK;AAAA,IACf;AACA,UAAM,UAAU,MAAM,OAAO,IAAI;AACjC,UAAM,UAAU,MAAM,OAAO,KAAK;AAClC,UAAM,QAAQ,WAAW,MAAM,OAAO,KAAK,GAAG,SAAS;AACvD,YAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AACzD,YAAQ,IAAI,IAAI,OAAO;AAAA,EACzB,CAAC;AACH;","names":[]}