@runeya/runeya 2.0.145 → 2.0.147

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 (194) hide show
  1. package/agent/index.js +36 -25
  2. package/agent/index.js.map +1 -1
  3. package/agent-manager-46C4NIQ2.js +13 -0
  4. package/chat-CMNDMXIP.js +28 -0
  5. package/{chunk-ONVAH65J.js → chunk-2NE52CL5.js} +422 -2172
  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-NCO4OOYT.js → chunk-4LJQIQMN.js} +2 -2
  10. package/{chunk-QF56Z6LI.js → chunk-5XG3X2TM.js} +29 -1481
  11. package/chunk-5XG3X2TM.js.map +1 -0
  12. package/chunk-6IBPK6UE.js +1063 -0
  13. package/chunk-6IBPK6UE.js.map +1 -0
  14. package/chunk-7QXP4FXD.js +969 -0
  15. package/chunk-7QXP4FXD.js.map +1 -0
  16. package/chunk-DA2DZBTL.js +43 -0
  17. package/chunk-DA2DZBTL.js.map +1 -0
  18. package/chunk-DFPZ4MTI.js +1041 -0
  19. package/chunk-DFPZ4MTI.js.map +1 -0
  20. package/{chunk-Q4RPY3VW.js → chunk-JNK6H5KH.js} +185 -1014
  21. package/chunk-JNK6H5KH.js.map +1 -0
  22. package/{chunk-FE53XX4M.js → chunk-NPMSBYXC.js} +3 -3
  23. package/{chunk-KS7A5CTW.js → chunk-VPSQJOSN.js} +12 -2
  24. package/{chunk-KS7A5CTW.js.map → chunk-VPSQJOSN.js.map} +1 -1
  25. package/chunk-WDHLEO5S.js +809 -0
  26. package/chunk-WDHLEO5S.js.map +1 -0
  27. package/{companion-HBYSY2D5.js → companion-XKMNRIJB.js} +10 -5
  28. package/environment-resolver-ZDTF3MHI.js +18 -0
  29. package/external-NMKQPROK.js +387 -0
  30. package/external-NMKQPROK.js.map +1 -0
  31. package/index.js +15 -6
  32. package/index.js.map +1 -1
  33. package/{install-gate-JAEK4BDZ.js → install-gate-OJJEUU4E.js} +2 -2
  34. package/mcp-ready-TAPBAOZY.js +9 -0
  35. package/mcp-server/external.d.ts +44 -0
  36. package/mcp-server/external.js +2679 -0
  37. package/mcp-server/external.js.map +1 -0
  38. package/mcp-server/index.js +159 -39
  39. package/mcp-server/index.js.map +1 -1
  40. package/mcp-server/launch.d.ts +25 -1
  41. package/mcp-server/launch.js +22 -1
  42. package/mcp-server/launch.js.map +1 -1
  43. package/package.json +1 -1
  44. package/project-store-FLXTAD3J.js +23 -0
  45. package/project-store-FLXTAD3J.js.map +1 -0
  46. package/settings-manager-XPLBO35P.js +10 -0
  47. package/settings-manager-XPLBO35P.js.map +1 -0
  48. package/{src-IODFUZ7A.js → src-KWO3CKZU.js} +1083 -551
  49. package/src-KWO3CKZU.js.map +1 -0
  50. package/{vault-resolver-PN3TYCLJ.js → vault-resolver-E7KPKA5C.js} +2 -2
  51. package/vault-resolver-E7KPKA5C.js.map +1 -0
  52. package/web/assets/{AgentScalarChatInterface.vue-npQgDxeP.js → AgentScalarChatInterface.vue-DKBinB_l.js} +2 -2
  53. package/web/assets/AgentTerminalPage-CixU7x6O.js +1 -0
  54. package/web/assets/AiChatModals-BvGUd-QD.css +1 -0
  55. package/web/assets/AiChatModals-C-hXImkO.js +4 -0
  56. package/web/assets/AppLayout-At9wk0sx.js +1 -0
  57. package/web/assets/{AppLayout-tsw5Ow0w.css → AppLayout-BlhwflMq.css} +1 -1
  58. package/web/assets/AuditPage-BkqbHxjY.js +1 -0
  59. package/web/assets/CloudAccountPage-D5C2SCS9.js +1 -0
  60. package/web/assets/CloudMessagingPage-Bi57GGHO.js +2 -0
  61. package/web/assets/DashboardPage-dEQop9ql.js +1 -0
  62. package/web/assets/DirectoryPickerDialog-DIMSeDhw.js +1 -0
  63. package/web/assets/{EditorIcon-CypGLV6w.js → EditorIcon-W3Szca4J.js} +1 -1
  64. package/web/assets/EnvironmentForm-C5emavNh.js +1 -0
  65. package/web/assets/EnvironmentFormPage-BIoy8fsW.js +1 -0
  66. package/web/assets/EnvironmentListPage-BDYeh7XF.js +1 -0
  67. package/web/assets/{FlagChip-R_T5qYpL.js → FlagChip-DPtIBCjQ.js} +1 -1
  68. package/web/assets/KanbanBoardPage-BlCcjBwV.js +4 -0
  69. package/web/assets/KanbanListPage-DbXONxfC.js +1 -0
  70. package/web/assets/LaunchProjectPage-Cg92hHwQ.js +1 -0
  71. package/web/assets/LaunchServicesPage-CO8gOB9D.js +1 -0
  72. package/web/assets/LaunchServicesPage-h5o3zLcy.css +1 -0
  73. package/web/assets/{LaunchShell-CGNYr-do.js → LaunchShell-CDb21ihZ.js} +1 -1
  74. package/web/assets/LogViewer-DsusKk2b.js +3 -0
  75. package/web/assets/OrgKeyPrompt-CX9K7r9I.js +1 -0
  76. package/web/assets/ProcessControls-B7hWVqiR.css +1 -0
  77. package/web/assets/ProcessControls-TEZutuxq.js +1 -0
  78. package/web/assets/ProcessDetailPage-Cj23FECq.js +1 -0
  79. package/web/assets/ProcessMetrics-1P6iE2Il.js +1 -0
  80. package/web/assets/ProjectCreateDialog-D3aMWKfw.js +1 -0
  81. package/web/assets/{ProjectLocationGate-bUd_tFpI.js → ProjectLocationGate-DU0Jj8Nw.js} +1 -1
  82. package/web/assets/ProjectPublishDialog-D7sNKei3.js +1 -0
  83. package/web/assets/{RandomJobTestPage-BFNC827R.js → RandomJobTestPage-DQUMRHyf.js} +1 -1
  84. package/web/assets/{ScenarioEditorPage-vMoRt0Qn.css → ScenarioEditorPage-BSjSX4bw.css} +1 -1
  85. package/web/assets/ScenarioEditorPage-DvCdcY9m.js +14 -0
  86. package/web/assets/SendToChatDialog-BdWKjYmv.js +1 -0
  87. package/web/assets/ServiceControlsBar-B_J5m-Kc.js +1 -0
  88. package/web/assets/ServiceDetailPage-DhJcjFfY.js +2 -0
  89. package/web/assets/{ServiceDetailPage-COdaCZ6r.css → ServiceDetailPage-DnSf5cm9.css} +1 -1
  90. package/web/assets/ServiceFormPage-fp8NCHSv.js +1 -0
  91. package/web/assets/SettingsPage-NH2fQap4.css +1 -0
  92. package/web/assets/SettingsPage-s28BodWe.js +2086 -0
  93. package/web/assets/UnlockPage-BXXIPHW_.js +1 -0
  94. package/web/assets/VaultMigrationPage-BfNuKtkJ.js +1 -0
  95. package/web/assets/{VersionBadge-DBquJl76.js → VersionBadge-CW7czacg.js} +1 -1
  96. package/web/assets/{WikiPage-GtwpJPqf.js → WikiPage-BiwpCM3E.js} +1 -1
  97. package/web/assets/WikiView-CMch_06Z.js +1 -0
  98. package/web/assets/{add-scalar-classes-DhSSqZ5l.js → add-scalar-classes-Ckanjq1h.js} +14 -14
  99. package/web/assets/{agent.store-Co44dMZf.js → agent.store-piokQ9Dt.js} +1 -1
  100. package/web/assets/{auth-Bk1GP5sB.js → auth-BT_jf-5e.js} +2 -2
  101. package/web/assets/cloud-chat.store-CDKh4tIy.js +1 -0
  102. package/web/assets/{cloud-client-0zD_NI6q.js → cloud-client-CzPdZLDq.js} +2 -2
  103. package/web/assets/{cloud-session-Cjw54Y--.js → cloud-session-BYfn6YM4.js} +2 -2
  104. package/web/assets/floating-ui.dom-DF3oyfHd.js +1 -0
  105. package/web/assets/i18n-D0FT7rCj.js +1 -0
  106. package/web/assets/index-3LTzgeU-.css +1 -0
  107. package/web/assets/index-Ci2CNnit.js +309 -0
  108. package/web/assets/modal-iRaP1izE.js +5 -0
  109. package/web/assets/notification.store-CvvuTBVz.js +1 -0
  110. package/web/assets/{project-launch.store-B5nhdGdh.js → project-launch.store-CTmbl8UY.js} +1 -1
  111. package/web/assets/{project.store-Bm2JWNbd.js → project.store-CNC6_4r4.js} +1 -1
  112. package/web/assets/runtime-dom.esm-bundler-CyEVRK6B.js +1 -0
  113. package/web/assets/scenario.store-BIFQPXgl.js +7 -0
  114. package/web/assets/{service.store-XS50iGKo.js → service.store-B40U4QzI.js} +1 -1
  115. package/web/assets/settings-scope-CyWMCjRY.js +1 -0
  116. package/web/assets/settings.store-Dw6bAoWE.js +1 -0
  117. package/web/assets/{src-Cgix2VOi.js → src-CrNi4Pjg.js} +3 -3
  118. package/web/assets/src-DIBHat7I.js +3 -0
  119. package/web/assets/src-DtCW_XCh.js +1 -0
  120. package/web/assets/{src-BDZKZr9n.css → src-g1DRo8xZ.css} +1 -1
  121. package/web/assets/{trpc-CfuFkKgp.js → trpc-BAZGRZBD.js} +3 -3
  122. package/web/assets/{useCloudOrg-J8DAnocE.js → useCloudOrg-EnXFBwt1.js} +1 -1
  123. package/web/assets/useEnvVarAutocomplete-BxJh2Lw0.js +1 -0
  124. package/web/assets/useProjectCatalog-BrvUGv9A.js +1 -0
  125. package/web/assets/{useProjectCloudOrg-BnlKts7x.js → useProjectCloudOrg-Czxlg00k.js} +1 -1
  126. package/web/assets/{useServiceUrlHost-hKbKtPMu.js → useServiceUrlHost-Szz9y3rh.js} +1 -1
  127. package/web/assets/vue-i18n-o7yraQpR.js +3 -0
  128. package/web/index.html +21 -21
  129. package/agent-manager-THV3LJOF.js +0 -11
  130. package/chat-XYCHMTFE.js +0 -23
  131. package/chunk-ONVAH65J.js.map +0 -1
  132. package/chunk-Q4RPY3VW.js.map +0 -1
  133. package/chunk-QF56Z6LI.js.map +0 -1
  134. package/settings-manager-QVQRJO6V.js +0 -10
  135. package/src-IODFUZ7A.js.map +0 -1
  136. package/web/assets/AgentTerminalPage-BTH3yCS2.js +0 -1
  137. package/web/assets/AiChatModals-EfRuvId7.css +0 -1
  138. package/web/assets/AiChatModals-It9jnB26.js +0 -4
  139. package/web/assets/AppLayout-BTg2CADo.js +0 -1
  140. package/web/assets/AuditPage-2PKKGLZs.js +0 -1
  141. package/web/assets/CloudAccountPage-CHL61gLe.js +0 -1
  142. package/web/assets/CloudMessagingPage-cgKnABPH.js +0 -2
  143. package/web/assets/DashboardPage-CRntTMKY.js +0 -1
  144. package/web/assets/DirectoryPickerDialog-DCyhT3-W.js +0 -1
  145. package/web/assets/EnvironmentForm-C96PFWRE.js +0 -1
  146. package/web/assets/EnvironmentFormPage-ChorAd63.js +0 -1
  147. package/web/assets/EnvironmentListPage-Dw9ziaI8.js +0 -1
  148. package/web/assets/KanbanBoardPage-BZHbEj-q.js +0 -4
  149. package/web/assets/KanbanListPage-B0jlfKqs.js +0 -1
  150. package/web/assets/LaunchProjectPage-BVs1JwbV.js +0 -1
  151. package/web/assets/LaunchServicesPage-DoLDPqQq.css +0 -1
  152. package/web/assets/LaunchServicesPage-PBYJly9N.js +0 -1
  153. package/web/assets/LogViewer-BDObx29F.js +0 -3
  154. package/web/assets/OrgKeyPrompt-BbcwiwGw.js +0 -1
  155. package/web/assets/ProcessControls-BlpnVylq.css +0 -1
  156. package/web/assets/ProcessControls-DY0HTKde.js +0 -1
  157. package/web/assets/ProcessDetailPage-BZ9kIQqn.js +0 -1
  158. package/web/assets/ProcessMetrics-Crlfkelj.js +0 -1
  159. package/web/assets/ProjectCreateDialog-C1wVDPcK.js +0 -1
  160. package/web/assets/ProjectPublishDialog-APUS1WEN.js +0 -1
  161. package/web/assets/ScenarioEditorPage-DFVsMVu5.js +0 -14
  162. package/web/assets/SendToChatDialog-QHIkxNio.js +0 -1
  163. package/web/assets/ServiceControlsBar-_UQXb6g0.js +0 -1
  164. package/web/assets/ServiceDetailPage-DGIXgHwR.js +0 -2
  165. package/web/assets/ServiceFormPage-RliAfS3_.js +0 -1
  166. package/web/assets/SettingsPage-BSSHxxjM.js +0 -2086
  167. package/web/assets/SettingsPage-W8YszhUL.css +0 -1
  168. package/web/assets/UnlockPage-17EgfpJs.js +0 -1
  169. package/web/assets/VaultMigrationPage-DeMxpXXJ.js +0 -1
  170. package/web/assets/WikiView-BqaqxMxW.js +0 -1
  171. package/web/assets/cloud-chat.store-CadB14dZ.js +0 -1
  172. package/web/assets/editor.store-DmM6ZlTm.js +0 -1
  173. package/web/assets/floating-ui.dom-DWhsZyqT.js +0 -1
  174. package/web/assets/i18n-8FjeJaKW.js +0 -1
  175. package/web/assets/index-B9DJf2V0.js +0 -309
  176. package/web/assets/index-teBqdnuv.css +0 -1
  177. package/web/assets/modal-UK8xRSnh.js +0 -5
  178. package/web/assets/scenario.store-88w6P6s2.js +0 -7
  179. package/web/assets/settings-scope-CKPq86HZ.js +0 -1
  180. package/web/assets/settings.store-BCJ0LJQA.js +0 -1
  181. package/web/assets/src--GO-VSJ0.js +0 -5
  182. package/web/assets/src-DBipSD56.js +0 -1
  183. package/web/assets/useEnvVarAutocomplete-C6zOXYqi.js +0 -1
  184. package/web/assets/useProjectCatalog-BB5IR6o8.js +0 -1
  185. package/web/assets/vue-draggable-plus-BHsveB8w.css +0 -1
  186. package/web/assets/vue-draggable-plus-D5ni4jMk.js +0 -1
  187. /package/{agent-manager-THV3LJOF.js.map → agent-manager-46C4NIQ2.js.map} +0 -0
  188. /package/{chat-XYCHMTFE.js.map → chat-CMNDMXIP.js.map} +0 -0
  189. /package/{chunk-NCO4OOYT.js.map → chunk-4LJQIQMN.js.map} +0 -0
  190. /package/{chunk-FE53XX4M.js.map → chunk-NPMSBYXC.js.map} +0 -0
  191. /package/{companion-HBYSY2D5.js.map → companion-XKMNRIJB.js.map} +0 -0
  192. /package/{settings-manager-QVQRJO6V.js.map → environment-resolver-ZDTF3MHI.js.map} +0 -0
  193. /package/{install-gate-JAEK4BDZ.js.map → install-gate-OJJEUU4E.js.map} +0 -0
  194. /package/{vault-resolver-PN3TYCLJ.js.map → mcp-ready-TAPBAOZY.js.map} +0 -0
@@ -1,3 +1,6 @@
1
+ import {
2
+ signalMcpReady
3
+ } from "./chunk-DA2DZBTL.js";
1
4
  import {
2
5
  ALLOWED_MIME_TYPES,
3
6
  APP_VERSION_HEADER,
@@ -19,6 +22,7 @@ import {
19
22
  applyPersistedLanMode,
20
23
  attachServiceToProject,
21
24
  auditService,
25
+ buildKanbanCardPrompt,
22
26
  buildServerBuiltins,
23
27
  canEnvAction,
24
28
  chatRouter,
@@ -27,11 +31,8 @@ import {
27
31
  cloudFetch2,
28
32
  cloudSessionStore,
29
33
  createContext,
30
- environmentOverridesStore,
31
- environmentResolver,
32
34
  executeKanbanBoardCommands,
33
35
  executeKanbanCommands,
34
- filterEnvForAi,
35
36
  generateLlmsTxt,
36
37
  getActiveProvider,
37
38
  getAllowedServiceIds,
@@ -39,6 +40,7 @@ import {
39
40
  getDeviceName,
40
41
  imageStorageService,
41
42
  isShareable,
43
+ kanbanContextFor,
42
44
  kanbanStore,
43
45
  logParserStore,
44
46
  maskApiKey,
@@ -49,11 +51,14 @@ import {
49
51
  readCloudIdentity,
50
52
  registerHttpServer,
51
53
  requireEnvAction,
54
+ requireHuman,
55
+ requireLoopback,
52
56
  requireLoopbackOrToken,
53
57
  requireServiceAction,
54
58
  resolveEffectiveAgentId,
55
59
  resolveServiceVariables,
56
60
  router,
61
+ runeyaMcpConfig,
57
62
  scenarioRunStore,
58
63
  scenarioStore,
59
64
  serviceRouter,
@@ -62,38 +67,56 @@ import {
62
67
  soleAccountScope,
63
68
  startCliSession,
64
69
  vaultOfProject
65
- } from "./chunk-ONVAH65J.js";
66
- import {
67
- settingsManager
68
- } from "./chunk-FE53XX4M.js";
70
+ } from "./chunk-2NE52CL5.js";
69
71
  import {
70
72
  CONVERSATIONS_DIRNAME,
71
- LocalProjectRefusedError,
72
73
  activeSessionRegistry,
73
74
  companionCreateDefaults,
74
75
  companionDefaultPrompt,
75
76
  companionSettings,
76
77
  conversationStore,
77
- ensureDataGitignore,
78
78
  generateJwtSecret,
79
79
  getJwtSecretGeneratedAt,
80
- holdsRuneyaProject,
81
- projectPathStore,
82
- projectStore,
83
80
  resolveAgentCwd,
84
81
  resolveConversationCwd,
85
82
  resolveWorkspaceCwd,
83
+ setMcpClientCheck,
86
84
  signJwt,
87
85
  startClaudeTranscriptWatcher,
88
86
  startCodexTranscriptWatcher,
89
87
  stopClaudeTranscriptWatcher,
90
88
  stopCodexTranscriptWatcher,
91
89
  verifyJwt
92
- } from "./chunk-Q4RPY3VW.js";
90
+ } from "./chunk-JNK6H5KH.js";
91
+ import "./chunk-6IBPK6UE.js";
92
+ import {
93
+ LocalProjectRefusedError,
94
+ ensureDataGitignore,
95
+ holdsRuneyaProject,
96
+ projectPathStore,
97
+ projectStore
98
+ } from "./chunk-7QXP4FXD.js";
99
+ import {
100
+ environmentOverridesStore,
101
+ environmentResolver,
102
+ filterEnvForAi
103
+ } from "./chunk-WDHLEO5S.js";
93
104
  import {
94
105
  agentManager,
95
106
  agentSpawner,
96
107
  agentStore,
108
+ killInstance,
109
+ listInstances,
110
+ registerInstance,
111
+ restartInstance,
112
+ scheduleSelfRestart,
113
+ traefikManager,
114
+ unregisterInstance
115
+ } from "./chunk-5XG3X2TM.js";
116
+ import {
117
+ serviceStore
118
+ } from "./chunk-4D3GCBPO.js";
119
+ import {
97
120
  clearDetached,
98
121
  clearEncryptionKey,
99
122
  clearLocalRemoval,
@@ -112,35 +135,31 @@ import {
112
135
  isEncryptedValue,
113
136
  isLocallyRemoved,
114
137
  isVaultLocked,
115
- killInstance,
116
- listInstances,
117
138
  loadFromEnv,
118
139
  markVaultLocked,
119
140
  onVaultUnlocked,
120
141
  projectKey,
121
- registerInstance,
122
- restartInstance,
123
- scheduleSelfRestart,
124
- serviceStore,
125
142
  setEncryptionKey,
126
143
  setEncryptionKeyInMemory,
127
144
  setPendingLegacyKey,
128
- stableEncryptValue,
129
- traefikManager,
130
- unregisterInstance
131
- } from "./chunk-QF56Z6LI.js";
145
+ stableEncryptValue
146
+ } from "./chunk-DFPZ4MTI.js";
132
147
  import {
133
148
  decryptValueWith,
134
149
  encryptValueWith,
150
+ isCliAvailable,
135
151
  resolveCliInvocation,
136
152
  whichCommand
137
153
  } from "./chunk-OZBSOSAS.js";
154
+ import {
155
+ settingsManager
156
+ } from "./chunk-NPMSBYXC.js";
138
157
  import {
139
158
  atomicWriteSecure,
140
159
  renameWithRetry,
141
160
  resolveConversationRunDir,
142
161
  sweepStaleTempFiles
143
- } from "./chunk-NCO4OOYT.js";
162
+ } from "./chunk-4LJQIQMN.js";
144
163
  import "./chunk-MD3VW4HQ.js";
145
164
  import {
146
165
  allVaultRoots,
@@ -148,6 +167,7 @@ import {
148
167
  env,
149
168
  forgetOrgLogo,
150
169
  keyRoot,
170
+ keyedFilePath,
151
171
  legacyMachineFilePath,
152
172
  legacyVaultRoot,
153
173
  listCloudVaults,
@@ -167,7 +187,7 @@ import {
167
187
  rememberOrgName,
168
188
  stampOrgCloud,
169
189
  vaultFor
170
- } from "./chunk-KS7A5CTW.js";
190
+ } from "./chunk-VPSQJOSN.js";
171
191
  import {
172
192
  AGENT_LONG_FETCH_TIMEOUT,
173
193
  AGENT_STOP_FETCH_TIMEOUT,
@@ -211,7 +231,7 @@ import {
211
231
  import express from "express";
212
232
  import cors from "cors";
213
233
  import { createServer } from "http";
214
- import { join as join17, resolve as resolve3 } from "path";
234
+ import { join as join18, resolve as resolve4 } from "path";
215
235
  import { WebSocketServer } from "ws";
216
236
  import { createExpressMiddleware } from "@trpc/server/adapters/express";
217
237
  import { applyWSSHandler } from "@trpc/server/adapters/ws";
@@ -226,7 +246,16 @@ import { z } from "zod";
226
246
  import { execFile } from "child_process";
227
247
  import { promisify } from "util";
228
248
  import { existsSync, mkdtempSync, readFileSync, rmdirSync } from "fs";
229
- import { dirname, isAbsolute, join, relative } from "path";
249
+ import { dirname, join } from "path";
250
+
251
+ // ../server/src/utils/is-inside.ts
252
+ import { isAbsolute, relative } from "path";
253
+ function isInside(dir, path) {
254
+ const rel = relative(dir, path);
255
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
256
+ }
257
+
258
+ // ../server/src/services/npm-cli.ts
230
259
  var execFileAsync = promisify(execFile);
231
260
  var PACKAGE_NAME = "@runeya/runeya";
232
261
  var INSTALL_TIMEOUT_MS = 10 * 6e4;
@@ -332,10 +361,6 @@ function installPrefix(globalPrefix, platform = process.platform) {
332
361
  const modules = platform === "win32" ? join(globalPrefix, "node_modules") : join(globalPrefix, "lib", "node_modules");
333
362
  return isWritable(modules) ? globalPrefix : userPrefix();
334
363
  }
335
- function isInside(dir, path) {
336
- const rel = relative(dir, path);
337
- return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
338
- }
339
364
  function updatePrefix(globalPrefix, runningEntry) {
340
365
  if (isInside(userPrefix(), runningEntry)) return userPrefix();
341
366
  if (!globalPrefix) return null;
@@ -642,6 +667,23 @@ function onCurrentProjectChanged(listener) {
642
667
  return () => listeners.delete(listener);
643
668
  }
644
669
 
670
+ // ../server/src/services/project-containing.ts
671
+ import { resolve } from "path";
672
+ function projectContaining(paths, path) {
673
+ const target = resolve(path);
674
+ let best = null;
675
+ for (const [key, dir] of Object.entries(paths)) {
676
+ const root = resolve(dir);
677
+ if (root !== target && !isInside(root, target)) continue;
678
+ if (!best || root.length > best.depth) best = { key, depth: root.length };
679
+ }
680
+ if (!best) return null;
681
+ const sep2 = best.key.indexOf(":");
682
+ if (sep2 === -1) return { id: best.key };
683
+ const owner = best.key.slice(0, sep2);
684
+ return { id: best.key.slice(sep2 + 1), orgId: owner === "local" ? null : owner };
685
+ }
686
+
645
687
  // ../server/src/services/org-keys.ts
646
688
  import { mkdir } from "fs/promises";
647
689
 
@@ -950,6 +992,13 @@ async function assertReadable(orgId) {
950
992
  throw err;
951
993
  }
952
994
  }
995
+ async function localProjectPaths() {
996
+ const paths = await projectPathStore.all();
997
+ for (const project of await projectStore.list()) {
998
+ if (!project.orgId) paths[`local:${project.id}`] = dirname2(vaultFor(project).root);
999
+ }
1000
+ return paths;
1001
+ }
953
1002
  var projectRouter = router({
954
1003
  list: protectedProcedure.query(async () => {
955
1004
  return projectStore.list();
@@ -1231,12 +1280,16 @@ var projectRouter = router({
1231
1280
  * Le dossier de chaque projet sur cette machine, par clé `org:projet`
1232
1281
  * (`local:<id>` pour un projet local, dont le dossier contient son `.runeya`).
1233
1282
  */
1234
- localPaths: protectedProcedure.query(async () => {
1235
- const paths = await projectPathStore.all();
1236
- for (const project of await projectStore.list()) {
1237
- if (!project.orgId) paths[`local:${project.id}`] = dirname2(vaultFor(project).root);
1238
- }
1239
- return paths;
1283
+ localPaths: protectedProcedure.query(() => localProjectPaths()),
1284
+ /**
1285
+ * Le projet dont le dossier contient ce chemin, ou `null`. Sert d'ancrage à
1286
+ * un outil IA lancé hors de Runeya (`runeya mcp`), qui sait seulement dans
1287
+ * quel dossier il tourne.
1288
+ */
1289
+ containing: protectedProcedure.input(z5.object({ path: z5.string().min(1).max(4096) })).query(async ({ input }) => {
1290
+ const ref = projectContaining(await localProjectPaths(), input.path);
1291
+ const project = ref ? await projectStore.get(ref).catch(() => null) : null;
1292
+ return project ? { projectId: project.id, orgId: project.orgId ?? null, name: project.name } : null;
1240
1293
  }),
1241
1294
  getLocalPath: protectedProcedure.input(z5.object({
1242
1295
  id: z5.string().min(1).max(128),
@@ -1334,8 +1387,8 @@ var projectRouter = router({
1334
1387
  touched.clear();
1335
1388
  yield { collections };
1336
1389
  } else {
1337
- await new Promise((resolve4) => {
1338
- wake = resolve4;
1390
+ await new Promise((resolve5) => {
1391
+ wake = resolve5;
1339
1392
  });
1340
1393
  }
1341
1394
  }
@@ -1483,13 +1536,13 @@ var agentRouter = router({
1483
1536
  yield { agentId: agent.config.id, status: agent.status };
1484
1537
  }
1485
1538
  const queue = [];
1486
- let resolve4 = null;
1539
+ let resolve5 = null;
1487
1540
  const handler = (event) => {
1488
1541
  if (queue.length >= SUBSCRIPTION_QUEUE_MAX) queue.shift();
1489
1542
  queue.push(event);
1490
- if (resolve4) {
1491
- resolve4();
1492
- resolve4 = null;
1543
+ if (resolve5) {
1544
+ resolve5();
1545
+ resolve5 = null;
1493
1546
  }
1494
1547
  };
1495
1548
  agentManager.on("status", handler);
@@ -1499,7 +1552,7 @@ var agentRouter = router({
1499
1552
  yield queue.shift();
1500
1553
  } else {
1501
1554
  await new Promise((r) => {
1502
- resolve4 = r;
1555
+ resolve5 = r;
1503
1556
  });
1504
1557
  }
1505
1558
  }
@@ -1519,12 +1572,26 @@ async function resolveTarget(processId, fallbackAgentId) {
1519
1572
  const service = await serviceStore.get(processId) ?? null;
1520
1573
  const agentId = (service ? await resolveEffectiveAgentId(service) : null) ?? fallbackAgentId ?? null;
1521
1574
  if (!agentId) {
1575
+ throw new TRPCError5({ code: "BAD_REQUEST", message: "NO_AGENT_FOR_SERVICE" });
1576
+ }
1577
+ return { agentId, service };
1578
+ }
1579
+ function isAgentUnreachable(err) {
1580
+ const e = err;
1581
+ if (!e) return false;
1582
+ if (e.cause?.code === "ECONNREFUSED" || e.cause?.code === "ECONNRESET" || e.cause?.code === "EHOSTUNREACH") return true;
1583
+ if (e.message === "fetch failed" || e.name === "TypeError" || e.name === "TimeoutError") return true;
1584
+ return /^Agent \S+ (not found|not connected)/.test(e.message ?? "");
1585
+ }
1586
+ async function callAgent(fn, fallbackMessage) {
1587
+ try {
1588
+ return await fn();
1589
+ } catch (err) {
1522
1590
  throw new TRPCError5({
1523
- code: "BAD_REQUEST",
1524
- message: "No agent assigned to this service. Set an agent on the service or on the active environment."
1591
+ code: "INTERNAL_SERVER_ERROR",
1592
+ message: isAgentUnreachable(err) ? "AGENT_UNREACHABLE" : err?.message ?? fallbackMessage
1525
1593
  });
1526
1594
  }
1527
- return { agentId, service };
1528
1595
  }
1529
1596
  async function assertEnvironmentComplete(service) {
1530
1597
  if (!service) return;
@@ -1563,26 +1630,17 @@ async function startProcess(processId, fallbackAgentId) {
1563
1630
  const { agentId, service } = await resolveTarget(processId, fallbackAgentId);
1564
1631
  await assertEnvironmentComplete(service);
1565
1632
  await deployBefore(processId, agentId, service, "start");
1566
- let result;
1567
- try {
1568
- result = await agentManager.agentMutation(agentId, "process.start", { id: processId });
1569
- } catch (err) {
1570
- const err_ = err;
1571
- const isFetchError = err_?.cause?.code === "ECONNREFUSED" || err_?.message === "fetch failed" || err_?.name === "TypeError";
1572
- throw new TRPCError5({
1573
- code: "INTERNAL_SERVER_ERROR",
1574
- message: isFetchError ? `Cannot reach agent \u2014 make sure the agent is running` : err_?.message ?? "Failed to start process"
1575
- });
1576
- }
1633
+ const result = await callAgent(
1634
+ () => agentManager.agentMutation(agentId, "process.start", { id: processId }),
1635
+ "Failed to start process"
1636
+ );
1577
1637
  await auditService.log({ userId: "local", action: "start", entityType: "process", entityId: processId, metadata: { agentId } });
1578
1638
  return { ...result, agentId };
1579
1639
  }
1580
1640
  async function stopProcess(processId, agentId) {
1581
- const result = await agentManager.agentMutation(
1582
- agentId,
1583
- "process.stop",
1584
- { id: processId },
1585
- AGENT_STOP_FETCH_TIMEOUT
1641
+ const result = await callAgent(
1642
+ () => agentManager.agentMutation(agentId, "process.stop", { id: processId }, AGENT_STOP_FETCH_TIMEOUT),
1643
+ "Failed to stop process"
1586
1644
  );
1587
1645
  await auditService.log({ userId: "local", action: "stop", entityType: "process", entityId: processId, metadata: { agentId } });
1588
1646
  return { ...result, agentId };
@@ -1596,7 +1654,10 @@ async function restartProcess(processId, fallbackAgentId) {
1596
1654
  console.debug("[process-control] Stop before restart ignored (process not found on agent?):", err.message);
1597
1655
  }
1598
1656
  await deployBefore(processId, agentId, service, "restart");
1599
- const result = await agentManager.agentMutation(agentId, "process.start", { id: processId });
1657
+ const result = await callAgent(
1658
+ () => agentManager.agentMutation(agentId, "process.start", { id: processId }),
1659
+ "Failed to restart process"
1660
+ );
1600
1661
  await auditService.log({ userId: "local", action: "restart", entityType: "process", entityId: processId, metadata: { agentId } });
1601
1662
  return { ...result, agentId };
1602
1663
  }
@@ -1618,14 +1679,14 @@ async function listAllProcesses() {
1618
1679
  var SUBSCRIPTION_QUEUE_MAX2 = LOG_BUFFER_SIZE;
1619
1680
  async function* forwardEvents(eventName, filter) {
1620
1681
  const queue = [];
1621
- let resolve4 = null;
1682
+ let resolve5 = null;
1622
1683
  const handler = (data) => {
1623
1684
  if (filter && !filter(data)) return;
1624
1685
  if (queue.length >= SUBSCRIPTION_QUEUE_MAX2) queue.shift();
1625
1686
  queue.push(data);
1626
- if (resolve4) {
1627
- resolve4();
1628
- resolve4 = null;
1687
+ if (resolve5) {
1688
+ resolve5();
1689
+ resolve5 = null;
1629
1690
  }
1630
1691
  };
1631
1692
  agentManager.on(eventName, handler);
@@ -1635,7 +1696,7 @@ async function* forwardEvents(eventName, filter) {
1635
1696
  yield queue.shift();
1636
1697
  } else {
1637
1698
  await new Promise((r) => {
1638
- resolve4 = r;
1699
+ resolve5 = r;
1639
1700
  });
1640
1701
  }
1641
1702
  }
@@ -1645,7 +1706,7 @@ async function* forwardEvents(eventName, filter) {
1645
1706
  }
1646
1707
  async function* forwardLogBatches(agentId, processId) {
1647
1708
  const queue = [];
1648
- let resolve4 = null;
1709
+ let resolve5 = null;
1649
1710
  const handler = (data) => {
1650
1711
  if (data.agentId !== agentId) return;
1651
1712
  const batch = data.batch;
@@ -1654,9 +1715,9 @@ async function* forwardLogBatches(agentId, processId) {
1654
1715
  if (filtered.length === 0) return;
1655
1716
  if (queue.length >= SUBSCRIPTION_QUEUE_MAX2) queue.shift();
1656
1717
  queue.push(filtered);
1657
- if (resolve4) {
1658
- resolve4();
1659
- resolve4 = null;
1718
+ if (resolve5) {
1719
+ resolve5();
1720
+ resolve5 = null;
1660
1721
  }
1661
1722
  };
1662
1723
  agentManager.on("process:log", handler);
@@ -1666,7 +1727,7 @@ async function* forwardLogBatches(agentId, processId) {
1666
1727
  yield queue.shift();
1667
1728
  } else {
1668
1729
  await new Promise((r) => {
1669
- resolve4 = r;
1730
+ resolve5 = r;
1670
1731
  });
1671
1732
  }
1672
1733
  }
@@ -2688,8 +2749,8 @@ var settingsRouter = router({
2688
2749
  pending = false;
2689
2750
  yield listWithOrigin(await logParserStore.list(vault), vault);
2690
2751
  } else {
2691
- await new Promise((resolve4) => {
2692
- wake = resolve4;
2752
+ await new Promise((resolve5) => {
2753
+ wake = resolve5;
2693
2754
  });
2694
2755
  }
2695
2756
  }
@@ -3046,7 +3107,7 @@ var settingsRouter = router({
3046
3107
  }
3047
3108
  } catch {
3048
3109
  }
3049
- await new Promise((resolve4) => setTimeout(resolve4, 500));
3110
+ await new Promise((resolve5) => setTimeout(resolve5, 500));
3050
3111
  }
3051
3112
  }),
3052
3113
  /**
@@ -3233,7 +3294,7 @@ import { z as z13 } from "zod";
3233
3294
  import { TRPCError as TRPCError9 } from "@trpc/server";
3234
3295
  import { execFile as execFile3, spawn as spawn2 } from "child_process";
3235
3296
  import { stat as stat2 } from "fs/promises";
3236
- import { resolve, join as join4 } from "path";
3297
+ import { resolve as resolve2, join as join4 } from "path";
3237
3298
  import { homedir as homedir2 } from "os";
3238
3299
 
3239
3300
  // ../server/src/services/user-config-manager.ts
@@ -3328,9 +3389,9 @@ async function getServiceCwd(serviceId) {
3328
3389
  return resolveCwd(await resolveWorkspaceCwd({ serviceId }));
3329
3390
  }
3330
3391
  async function isCommandAvailable(command) {
3331
- return new Promise((resolve4) => {
3392
+ return new Promise((resolve5) => {
3332
3393
  execFile3(whichCommand(), [command], { timeout: 5e3, windowsHide: true }, (err) => {
3333
- resolve4(!err);
3394
+ resolve5(!err);
3334
3395
  });
3335
3396
  });
3336
3397
  }
@@ -3401,7 +3462,7 @@ var editorRouter = router({
3401
3462
  throw new TRPCError9({ code: "BAD_REQUEST", message: `Unknown editor: ${input.editorKey}` });
3402
3463
  }
3403
3464
  const expanded = input.path.replace(/^~(\/|$)/, `${process.env["HOME"] ?? "/tmp"}$1`).replace(/\$\{(\w+)\}|\$(\w+)/g, (_, a, b) => process.env[a || b] ?? "");
3404
- const resolvedPath = resolve(expanded || ".");
3465
+ const resolvedPath = resolve2(expanded || ".");
3405
3466
  try {
3406
3467
  await stat2(resolvedPath);
3407
3468
  } catch (err) {
@@ -3708,7 +3769,7 @@ Rules:
3708
3769
  onStatusChange: protectedProcedure.input(z15.object({ ...gitTargetShape })).subscription(async function* ({ input }) {
3709
3770
  const { agentId, cwd } = await resolveGitContext(input);
3710
3771
  const queue = [];
3711
- let resolve4 = null;
3772
+ let resolve5 = null;
3712
3773
  let done = false;
3713
3774
  let unsubscribe;
3714
3775
  try {
@@ -3718,16 +3779,16 @@ Rules:
3718
3779
  { cwd },
3719
3780
  (data) => {
3720
3781
  queue.push(data);
3721
- if (resolve4) {
3722
- resolve4();
3723
- resolve4 = null;
3782
+ if (resolve5) {
3783
+ resolve5();
3784
+ resolve5 = null;
3724
3785
  }
3725
3786
  },
3726
3787
  () => {
3727
3788
  done = true;
3728
- if (resolve4) {
3729
- resolve4();
3730
- resolve4 = null;
3789
+ if (resolve5) {
3790
+ resolve5();
3791
+ resolve5 = null;
3731
3792
  }
3732
3793
  }
3733
3794
  );
@@ -3743,7 +3804,7 @@ Rules:
3743
3804
  yield queue.shift();
3744
3805
  } else {
3745
3806
  await new Promise((r) => {
3746
- resolve4 = r;
3807
+ resolve5 = r;
3747
3808
  });
3748
3809
  }
3749
3810
  }
@@ -4091,21 +4152,21 @@ async function* tapBus(conversationId, signal) {
4091
4152
  }
4092
4153
  if (bus.done) return;
4093
4154
  if (signal.aborted) return;
4094
- await new Promise((resolve4) => {
4155
+ await new Promise((resolve5) => {
4095
4156
  const onAbort = () => {
4096
4157
  bus.listeners.delete(notify);
4097
- resolve4();
4158
+ resolve5();
4098
4159
  };
4099
4160
  const notify = () => {
4100
4161
  signal.removeEventListener("abort", onAbort);
4101
- resolve4();
4162
+ resolve5();
4102
4163
  };
4103
4164
  bus.listeners.add(notify);
4104
4165
  signal.addEventListener("abort", onAbort, { once: true });
4105
4166
  if (cursor < bus.baseIndex + bus.buffer.length || bus.done || signal.aborted) {
4106
4167
  bus.listeners.delete(notify);
4107
4168
  signal.removeEventListener("abort", onAbort);
4108
- resolve4();
4169
+ resolve5();
4109
4170
  }
4110
4171
  });
4111
4172
  }
@@ -4545,6 +4606,39 @@ function createNodeRunnerState(aiCli) {
4545
4606
  isResume: false
4546
4607
  };
4547
4608
  }
4609
+ async function nodeRuneyaAccess(conversationId) {
4610
+ const apiBaseUrl = `http://localhost:${env.PORT}`;
4611
+ const apiToken = await signJwt();
4612
+ const conv = await conversationStore.get(conversationId);
4613
+ const ref = conv ? await conversationStore.projectRefOf(conv) : null;
4614
+ let injectedEnv;
4615
+ if (ref?.id) {
4616
+ try {
4617
+ const { projectStore: projectStore2 } = await import("./project-store-FLXTAD3J.js");
4618
+ const { environmentResolver: environmentResolver2 } = await import("./environment-resolver-ZDTF3MHI.js");
4619
+ const project = await projectStore2.get(ref.orgId ? { id: ref.id, orgId: ref.orgId } : ref.id);
4620
+ if (project?.activeEnvironmentId) {
4621
+ const injected = await environmentResolver2.resolveAiInjectedEnv(project.activeEnvironmentId);
4622
+ if (Object.keys(injected).length > 0) injectedEnv = injected;
4623
+ }
4624
+ } catch (err) {
4625
+ console.warn("[scenario-runner] injected env unavailable:", err.message);
4626
+ }
4627
+ }
4628
+ return {
4629
+ apiBaseUrl,
4630
+ apiToken,
4631
+ mcpConfig: runeyaMcpConfig({
4632
+ RUNEYA_BASE_URL: apiBaseUrl,
4633
+ RUNEYA_API_TOKEN: apiToken,
4634
+ ...ref?.id ? { RUNEYA_PROJECT_ID: ref.id } : {},
4635
+ ...ref?.orgId ? { RUNEYA_ORG_ID: ref.orgId } : {}
4636
+ }),
4637
+ ...ref?.id ? { projectId: ref.id } : {},
4638
+ ...ref?.orgId ? { orgId: ref.orgId } : {},
4639
+ ...injectedEnv ? { injectedEnv } : {}
4640
+ };
4641
+ }
4548
4642
  async function runNodeCall(conversationId, node, systemContext, userMessage, runOverrides, signal, publish2, onDelta, runnerStates, label = "call", readme) {
4549
4643
  publish2({ type: "node_prompt", nodeId: node.id, label, systemContext, userMessage });
4550
4644
  const { aiCli, model } = resolveNodeExecutionConfig(node, runOverrides);
@@ -4569,6 +4663,10 @@ async function runNodeCall(conversationId, node, systemContext, userMessage, run
4569
4663
  persistTimer = null;
4570
4664
  }
4571
4665
  };
4666
+ const access3 = await nodeRuneyaAccess(conversationId).catch((err) => {
4667
+ console.warn("[scenario-runner] node runs without Runeya tools:", err.message);
4668
+ return { apiBaseUrl: "", apiToken: "" };
4669
+ });
4572
4670
  while (true) {
4573
4671
  const turnStart = Date.now();
4574
4672
  const result = await state.runner.run({
@@ -4583,8 +4681,7 @@ async function runNodeCall(conversationId, node, systemContext, userMessage, run
4583
4681
  prompt: currentPrompt,
4584
4682
  isResume: state.isResume,
4585
4683
  systemContext,
4586
- apiBaseUrl: "",
4587
- apiToken: "",
4684
+ ...access3,
4588
4685
  model,
4589
4686
  effort: "normal",
4590
4687
  signal,
@@ -4615,9 +4712,9 @@ async function runNodeCall(conversationId, node, systemContext, userMessage, run
4615
4712
  publish2({ type: "node_waiting_input", nodeId: node.id, question: result.tokenMessage });
4616
4713
  await readme?.(`- \u2753 [${formatTs()}] **${node.label}** \u2014 waiting for user input
4617
4714
  `);
4618
- const answer = await new Promise((resolve4, reject) => {
4715
+ const answer = await new Promise((resolve5, reject) => {
4619
4716
  const key = pendingNodeKey(conversationId, node.id);
4620
- pendingNodeInputs.set(key, resolve4);
4717
+ pendingNodeInputs.set(key, resolve5);
4621
4718
  signal.addEventListener("abort", () => {
4622
4719
  pendingNodeInputs.delete(key);
4623
4720
  reject(new Error("Aborted"));
@@ -4756,6 +4853,18 @@ async function runScenario(conversationId, scenarioId, userRequest, runOverrides
4756
4853
  })();
4757
4854
  const leader = scenario.nodes.find((p) => p.id === scenario.leaderId);
4758
4855
  if (!leader) throw new Error("Scenario has no leader");
4856
+ const nodeRequest = userRequest + await (async () => {
4857
+ const conv = await conversationStore.get(conversationId);
4858
+ if (!conv?.kanbanCardId) return "";
4859
+ const ref = await conversationStore.projectRefOf(conv);
4860
+ return kanbanContextFor(conv, {
4861
+ ...ref?.id ? { projectId: ref.id } : {},
4862
+ ...ref?.orgId ? { orgId: ref.orgId } : {}
4863
+ });
4864
+ })().catch((err) => {
4865
+ console.warn("[scenario-runner] card context unavailable:", err.message);
4866
+ return "";
4867
+ });
4759
4868
  const byParent = /* @__PURE__ */ new Map();
4760
4869
  const nodesById = new Map(scenario.nodes.map((p) => [p.id, p]));
4761
4870
  const outputs = /* @__PURE__ */ new Map();
@@ -4811,7 +4920,7 @@ async function runScenario(conversationId, scenarioId, userRequest, runOverrides
4811
4920
  await li("\u25B6\uFE0F", `**${leader.label}** \u2014 started`);
4812
4921
  try {
4813
4922
  const systemPrompt = buildSystemPrompt(leader, scenario);
4814
- const userMsg = buildUserMessage(leader, scenario, userRequest, null, false, true);
4923
+ const userMsg = buildUserMessage(leader, scenario, nodeRequest, null, false, true);
4815
4924
  const out = await runNodeCall(conversationId, leader, systemPrompt, userMsg, runOverrides, signal, publish2, (delta) => {
4816
4925
  publish2({ type: "node_delta", nodeId: leader.id, text: delta });
4817
4926
  }, runnerStates, "dispatch", readme);
@@ -4842,7 +4951,7 @@ async function runScenario(conversationId, scenarioId, userRequest, runOverrides
4842
4951
  if (hasUnknown) await li("\u26A0\uFE0F", `Unknown node(s) named by ${leader.label}: ${leaderResult.unknownNames.join(", ")}. Retrying.`);
4843
4952
  else if (hasMissingAssignments) await li("\u26A0\uFE0F", `Missing assignments for ${leaderResult.missingAssignments.join(", ")} from ${leader.label}. Retrying.`);
4844
4953
  else await li("\u26A0\uFE0F", `No explicit assignees parsed for ${leader.label}. Retrying once to force explicit selection.`);
4845
- const retryUserMsg = hasMissingAssignments ? buildMissingAssignmentsRetryMessage(leader, leaderResult.missingAssignments, out) : buildDelegationRetryMessage(leader, directReports, userRequest, out);
4954
+ const retryUserMsg = hasMissingAssignments ? buildMissingAssignmentsRetryMessage(leader, leaderResult.missingAssignments, out) : buildDelegationRetryMessage(leader, directReports, nodeRequest, out);
4846
4955
  runnerStates.delete(leader.id);
4847
4956
  const retryOut = await runNodeCall(conversationId, leader, systemPrompt, retryUserMsg, runOverrides, signal, publish2, (delta) => {
4848
4957
  publish2({ type: "node_delta", nodeId: leader.id, text: delta });
@@ -4943,7 +5052,7 @@ ${leaderErrorOutput}` });
4943
5052
  if (hasUnknown) await li("\u26A0\uFE0F", `Unknown node(s) named by ${node.label}: ${nodeResult.unknownNames.join(", ")}. Retrying.`);
4944
5053
  else if (hasMissingAssignments) await li("\u26A0\uFE0F", `Missing assignments for ${nodeResult.missingAssignments.join(", ")} from ${node.label}. Retrying.`);
4945
5054
  else await li("\u26A0\uFE0F", `No explicit assignees parsed for ${node.label}. Retrying once to force explicit selection.`);
4946
- const retryUserMsg = hasMissingAssignments ? buildMissingAssignmentsRetryMessage(node, nodeResult.missingAssignments, out) : buildDelegationRetryMessage(node, directReports, userRequest, out);
5055
+ const retryUserMsg = hasMissingAssignments ? buildMissingAssignmentsRetryMessage(node, nodeResult.missingAssignments, out) : buildDelegationRetryMessage(node, directReports, nodeRequest, out);
4947
5056
  runnerStates.delete(node.id);
4948
5057
  const retryOut = await runNodeCall(conversationId, node, systemPrompt, retryUserMsg, runOverrides, signal, publish2, (delta) => {
4949
5058
  publish2({ type: "node_delta", nodeId: node.id, text: delta });
@@ -5022,9 +5131,9 @@ ${outputs.get(node.id) ?? ""}`.trim();
5022
5131
  publish2({ type: "node_waiting_input", nodeId: node.id, question });
5023
5132
  await readme(`- \u2753 [${ts()}] **${node.label || node.id}** \u2014 waiting for user input
5024
5133
  `);
5025
- const answer = await new Promise((resolve4, reject) => {
5134
+ const answer = await new Promise((resolve5, reject) => {
5026
5135
  const key = pendingNodeKey(conversationId, node.id);
5027
- pendingNodeInputs.set(key, resolve4);
5136
+ pendingNodeInputs.set(key, resolve5);
5028
5137
  signal.addEventListener("abort", () => {
5029
5138
  pendingNodeInputs.delete(key);
5030
5139
  reject(new Error("Aborted"));
@@ -5069,7 +5178,7 @@ ${outputs.get(node.id) ?? ""}`.trim();
5069
5178
  await li("\u267B\uFE0F", `**${node.label}** \u2014 restored from previous run`);
5070
5179
  firstPassResult = { finalOutput: cachedOut, assignedReports: result.selected, assignments: result.assignments };
5071
5180
  } else {
5072
- const initialUserMsg = buildUserMessage(node, scenario, userRequest, managerOutput, false, false);
5181
+ const initialUserMsg = buildUserMessage(node, scenario, nodeRequest, managerOutput, false, false, new Map(outputs));
5073
5182
  firstPassResult = await runDelegationPass(node, initialUserMsg, "(execution)");
5074
5183
  }
5075
5184
  let { finalOutput, assignedReports, assignments } = firstPassResult;
@@ -5096,7 +5205,7 @@ ${reportOut}
5096
5205
  const reviewUserMsg = buildManagerReviewMessage(
5097
5206
  node,
5098
5207
  getDirectReports(scenario.nodes, node.id),
5099
- userRequest,
5208
+ nodeRequest,
5100
5209
  finalOutput,
5101
5210
  childReportsSection
5102
5211
  );
@@ -5162,7 +5271,7 @@ ${reportOut}
5162
5271
  try {
5163
5272
  const parentOutput = node.parentIds[0] ? outputs.get(node.parentIds[0]) ?? null : null;
5164
5273
  const systemPrompt = buildSystemPrompt(node, scenario);
5165
- const userMsg = buildUserMessage(node, scenario, userRequest, parentOutput, false, false, new Map(outputs));
5274
+ const userMsg = buildUserMessage(node, scenario, nodeRequest, parentOutput, false, false, new Map(outputs));
5166
5275
  const out = await runNodeCall(conversationId, node, systemPrompt, userMsg, runOverrides, signal, publish2, (delta) => {
5167
5276
  publish2({ type: "node_delta", nodeId: node.id, text: delta });
5168
5277
  }, runnerStates, "(topological)", readme);
@@ -5214,7 +5323,7 @@ ${reportOut}
5214
5323
  const leaderReviewMsg = buildManagerReviewMessage(
5215
5324
  leader,
5216
5325
  getDirectReports(scenario.nodes, leader.id),
5217
- userRequest,
5326
+ nodeRequest,
5218
5327
  leaderCurrentOutput,
5219
5328
  leaderChildReportsSection
5220
5329
  );
@@ -5247,7 +5356,7 @@ ${reportOut}
5247
5356
  await li("\u25B6\uFE0F", `**${leader.label}** synthesis \u2014 started`);
5248
5357
  try {
5249
5358
  const systemPrompt = buildSystemPrompt(leader, scenario);
5250
- const userMsg = buildUserMessage(leader, scenario, userRequest, null, true, false, outputs);
5359
+ const userMsg = buildUserMessage(leader, scenario, nodeRequest, null, true, false, outputs);
5251
5360
  let summary = "";
5252
5361
  summary = await runNodeCall(conversationId, leader, systemPrompt, userMsg, runOverrides, signal, publish2, (delta) => {
5253
5362
  publish2({ type: "node_delta", nodeId: `${leader.id}:synthesis`, text: delta });
@@ -5398,7 +5507,7 @@ var scenarioRouter = router({
5398
5507
  isPublished: input.isPublished ?? false,
5399
5508
  createdAt: now,
5400
5509
  updatedAt: now
5401
- }));
5510
+ }, await scopeOf2(input)));
5402
5511
  }),
5403
5512
  update: protectedProcedure.input(z16.object({
5404
5513
  id: Id128Schema,
@@ -5444,7 +5553,8 @@ var scenarioRouter = router({
5444
5553
  orgId: z16.string().max(128).nullable().optional(),
5445
5554
  resume: z16.boolean().optional()
5446
5555
  })).subscription(async function* ({ input, signal }) {
5447
- const scenario = await scenarioStore.get(input.scenarioId, await scopeOf2(input));
5556
+ const vault = await scopeOf2(input);
5557
+ const scenario = await scenarioStore.get(input.scenarioId, vault);
5448
5558
  if (!scenario) throw new TRPCError14({ code: "NOT_FOUND", message: "Scenario not found" });
5449
5559
  const canStart = input.userRequest.trim().length > 0 || !!input.resume;
5450
5560
  if (!isScenarioRunning(input.conversationId) && canStart) {
@@ -5456,7 +5566,7 @@ var scenarioRouter = router({
5456
5566
  runnerOverride: input.runnerOverride,
5457
5567
  modelOverride: input.modelOverride,
5458
5568
  agentId: input.agentId,
5459
- orgId: input.orgId
5569
+ orgId: vault ?? null
5460
5570
  }, !!input.resume);
5461
5571
  await new Promise((r) => setTimeout(r, 10));
5462
5572
  }
@@ -5577,21 +5687,21 @@ async function* tapJob(job, signal) {
5577
5687
  yield evt;
5578
5688
  }
5579
5689
  if (job.done || signal.aborted) return;
5580
- await new Promise((resolve4) => {
5690
+ await new Promise((resolve5) => {
5581
5691
  const onAbort = () => {
5582
5692
  job.listeners.delete(notify);
5583
- resolve4();
5693
+ resolve5();
5584
5694
  };
5585
5695
  const notify = () => {
5586
5696
  signal.removeEventListener("abort", onAbort);
5587
- resolve4();
5697
+ resolve5();
5588
5698
  };
5589
5699
  job.listeners.add(notify);
5590
5700
  signal.addEventListener("abort", onAbort, { once: true });
5591
5701
  if (cursor < job.baseIndex + job.buffer.length || job.done || signal.aborted) {
5592
5702
  job.listeners.delete(notify);
5593
5703
  signal.removeEventListener("abort", onAbort);
5594
- resolve4();
5704
+ resolve5();
5595
5705
  }
5596
5706
  });
5597
5707
  }
@@ -5743,9 +5853,9 @@ var diagnosticsRouter = router({
5743
5853
  try {
5744
5854
  while (!signal?.aborted) {
5745
5855
  if (queue.length === 0) {
5746
- await new Promise((resolve4) => {
5747
- wake = resolve4;
5748
- signal?.addEventListener("abort", () => resolve4(), { once: true });
5856
+ await new Promise((resolve5) => {
5857
+ wake = resolve5;
5858
+ signal?.addEventListener("abort", () => resolve5(), { once: true });
5749
5859
  });
5750
5860
  wake = null;
5751
5861
  continue;
@@ -5817,11 +5927,395 @@ var diagnosticsRouter = router({
5817
5927
  })
5818
5928
  });
5819
5929
 
5820
- // ../server/src/trpc/routers/kanban-queue.ts
5930
+ // ../server/src/trpc/routers/mcp-pairing.ts
5931
+ import { z as z20 } from "zod";
5932
+ import { TRPCError as TRPCError17 } from "@trpc/server";
5933
+
5934
+ // ../server/src/services/mcp-pairing.ts
5935
+ import { createHash, randomBytes as randomBytes2, randomInt, randomUUID as randomUUID5, timingSafeEqual } from "crypto";
5936
+ import { readFile as readFile3 } from "fs/promises";
5821
5937
  import { z as z19 } from "zod";
5938
+ var REQUEST_TTL_MS = 2 * 60 * 1e3;
5939
+ var MCP_TOKEN_TTL = "15m";
5940
+ var FILE = "mcp-clients.json";
5941
+ var McpClientSchema = z19.object({
5942
+ id: z19.string().uuid(),
5943
+ name: z19.string().min(1).max(100),
5944
+ cwd: z19.string().max(4096),
5945
+ /** SHA-256 du secret, en hexadécimal. */
5946
+ secretHash: z19.string().regex(/^[0-9a-f]{64}$/),
5947
+ createdAt: z19.string().max(64)
5948
+ });
5949
+ var McpClientsFileSchema = z19.object({ clients: z19.array(McpClientSchema).max(500) });
5950
+ function hashSecret(secret) {
5951
+ return createHash("sha256").update(secret).digest("hex");
5952
+ }
5953
+ var McpPairing = class {
5954
+ constructor(filePath, now = Date.now) {
5955
+ this.filePath = filePath;
5956
+ this.now = now;
5957
+ }
5958
+ requests = /* @__PURE__ */ new Map();
5959
+ /**
5960
+ * Relu à chaque accès, sans copie en mémoire. Plusieurs Runeya d'un poste
5961
+ * partagent ce fichier : une copie mémoire ferait réécrire à l'une la liste
5962
+ * d'avant l'accord donné par l'autre, et cacherait ses révocations. Le
5963
+ * fichier tient en quelques lignes ; le relire coûte moins qu'un tel oubli.
5964
+ */
5965
+ async load() {
5966
+ const path = await this.filePath();
5967
+ let text;
5968
+ try {
5969
+ text = await readFile3(path, "utf8");
5970
+ } catch (err) {
5971
+ if (err.code === "ENOENT") return [];
5972
+ throw err;
5973
+ }
5974
+ const unreadable = () => new Error(`${path} est illisible : les acc\xE8s MCP ne sont pas modifi\xE9s.`);
5975
+ let raw;
5976
+ try {
5977
+ raw = JSON.parse(text);
5978
+ } catch {
5979
+ throw unreadable();
5980
+ }
5981
+ const parsed = McpClientsFileSchema.safeParse(raw);
5982
+ if (!parsed.success) throw unreadable();
5983
+ return parsed.data.clients;
5984
+ }
5985
+ async save(clients) {
5986
+ await atomicWriteSecure(await this.filePath(), JSON.stringify({ clients }, null, 2));
5987
+ }
5988
+ sweep() {
5989
+ for (const [id, r] of this.requests) {
5990
+ if (this.now() - r.createdAt > REQUEST_TTL_MS) this.requests.delete(id);
5991
+ }
5992
+ }
5993
+ toPublic(r) {
5994
+ return { id: r.id, name: r.name, cwd: r.cwd, code: r.code, expiresAt: r.createdAt + REQUEST_TTL_MS };
5995
+ }
5996
+ /** Une demande d'accès. Refusée tant qu'une autre attend une réponse. */
5997
+ request(name, cwd) {
5998
+ this.sweep();
5999
+ if ([...this.requests.values()].some((r2) => r2.status === "pending")) {
6000
+ throw new Error("Une autre demande d'acc\xE8s attend d\xE9j\xE0 une r\xE9ponse dans Runeya.");
6001
+ }
6002
+ const r = {
6003
+ id: randomUUID5(),
6004
+ name,
6005
+ cwd,
6006
+ code: String(randomInt(0, 1e4)).padStart(4, "0"),
6007
+ createdAt: this.now(),
6008
+ status: "pending"
6009
+ };
6010
+ this.requests.set(r.id, r);
6011
+ return this.toPublic(r);
6012
+ }
6013
+ /**
6014
+ * Où en est une demande. Le secret n'est rendu qu'une fois : la demande est
6015
+ * oubliée dès qu'il est remis.
6016
+ */
6017
+ status(requestId) {
6018
+ this.sweep();
6019
+ const r = this.requests.get(requestId);
6020
+ if (!r) return { status: "expired" };
6021
+ if (r.status === "approved") {
6022
+ this.requests.delete(requestId);
6023
+ return { status: "approved", secret: r.secret };
6024
+ }
6025
+ if (r.status === "denied") this.requests.delete(requestId);
6026
+ return { status: r.status };
6027
+ }
6028
+ pending() {
6029
+ this.sweep();
6030
+ return [...this.requests.values()].filter((r) => r.status === "pending").map((r) => this.toPublic(r));
6031
+ }
6032
+ async decide(requestId, approve) {
6033
+ this.sweep();
6034
+ const r = this.requests.get(requestId);
6035
+ if (!r || r.status !== "pending") throw new Error("Cette demande n'existe plus ou a expir\xE9.");
6036
+ if (!approve) {
6037
+ r.status = "denied";
6038
+ return;
6039
+ }
6040
+ const id = randomUUID5();
6041
+ const secret = `${id}.${randomBytes2(32).toString("hex")}`;
6042
+ const clients = await this.load();
6043
+ await this.save([...clients, {
6044
+ id,
6045
+ name: r.name,
6046
+ cwd: r.cwd,
6047
+ secretHash: hashSecret(secret),
6048
+ createdAt: new Date(this.now()).toISOString()
6049
+ }]);
6050
+ r.status = "approved";
6051
+ r.secret = secret;
6052
+ }
6053
+ /** Échange le secret d'un client appairé contre un jeton court. */
6054
+ async exchange(secret) {
6055
+ const id = secret.split(".")[0] ?? "";
6056
+ const client = (await this.load()).find((c) => c.id === id);
6057
+ const expected = Buffer.from(client?.secretHash ?? "0".repeat(64), "hex");
6058
+ const match = timingSafeEqual(expected, Buffer.from(hashSecret(secret), "hex"));
6059
+ if (!client || !match) throw new Error("Appairage inconnu ou r\xE9voqu\xE9.");
6060
+ return signJwt({ mcpClientId: client.id, ttl: MCP_TOKEN_TTL });
6061
+ }
6062
+ /** Faux aussi quand le fichier est illisible : dans le doute, l'accès est refusé. */
6063
+ async isPaired(clientId) {
6064
+ return this.load().then((clients) => clients.some((c) => c.id === clientId), () => false);
6065
+ }
6066
+ async list() {
6067
+ return (await this.load()).map(({ secretHash: _, ...rest }) => rest);
6068
+ }
6069
+ async revoke(clientId) {
6070
+ const clients = await this.load();
6071
+ await this.save(clients.filter((c) => c.id !== clientId));
6072
+ }
6073
+ };
6074
+ var mcpPairing = new McpPairing(() => keyedFilePath(FILE));
6075
+
6076
+ // ../server/src/trpc/routers/mcp-pairing.ts
6077
+ function asBadRequest(fn) {
6078
+ return Promise.resolve().then(fn).catch((err) => {
6079
+ throw new TRPCError17({ code: "BAD_REQUEST", message: err.message });
6080
+ });
6081
+ }
6082
+ var mcpPairingRouter = router({
6083
+ request: publicProcedure.input(z20.object({ name: z20.string().min(1).max(100), cwd: z20.string().max(4096) })).mutation(({ ctx, input }) => {
6084
+ requireLoopback(ctx);
6085
+ if (isCiMode()) {
6086
+ throw new TRPCError17({
6087
+ code: "PRECONDITION_FAILED",
6088
+ message: "Runeya tourne en mode CI : l'appairage y est d\xE9sactiv\xE9. Passe une cl\xE9 d'API dans RUNEYA_API_TOKEN."
6089
+ });
6090
+ }
6091
+ return asBadRequest(() => mcpPairing.request(input.name, input.cwd));
6092
+ }),
6093
+ status: publicProcedure.input(z20.object({ requestId: z20.string().uuid() })).query(({ ctx, input }) => {
6094
+ requireLoopback(ctx);
6095
+ return mcpPairing.status(input.requestId);
6096
+ }),
6097
+ exchange: publicProcedure.input(z20.object({ secret: z20.string().min(1).max(200) })).mutation(({ ctx, input }) => {
6098
+ requireLoopback(ctx);
6099
+ return mcpPairing.exchange(input.secret).then((token) => ({ token })).catch(() => {
6100
+ throw new TRPCError17({ code: "UNAUTHORIZED", message: "Unknown or revoked MCP pairing." });
6101
+ });
6102
+ }),
6103
+ pending: protectedProcedure.query(({ ctx }) => {
6104
+ requireHuman(ctx);
6105
+ return mcpPairing.pending();
6106
+ }),
6107
+ decide: protectedProcedure.input(z20.object({ requestId: z20.string().uuid(), approve: z20.boolean() })).mutation(({ ctx, input }) => {
6108
+ requireHuman(ctx);
6109
+ return asBadRequest(() => mcpPairing.decide(input.requestId, input.approve));
6110
+ }),
6111
+ clients: protectedProcedure.query(({ ctx }) => {
6112
+ requireHuman(ctx);
6113
+ return mcpPairing.list();
6114
+ }),
6115
+ revoke: protectedProcedure.input(z20.object({ id: z20.string().uuid() })).mutation(({ ctx, input }) => {
6116
+ requireHuman(ctx);
6117
+ return mcpPairing.revoke(input.id);
6118
+ })
6119
+ });
6120
+
6121
+ // ../server/src/trpc/routers/ai-clients.ts
6122
+ import { z as z21 } from "zod";
6123
+ import { TRPCError as TRPCError18 } from "@trpc/server";
6124
+
6125
+ // ../server/src/services/ai-client-mcp.ts
6126
+ import { execFile as execFile4 } from "child_process";
6127
+ import { existsSync as existsSync2 } from "fs";
6128
+ import { mkdir as mkdir5, readFile as readFile4, realpath } from "fs/promises";
6129
+ import { homedir as homedir3 } from "os";
6130
+ import { dirname as dirname4, join as join7 } from "path";
6131
+ var SERVER_NAME = "runeya";
6132
+ function mcpEntry(override = env.MCP_COMMAND ?? null, home = env.HOME_DIR, platform = process.platform) {
6133
+ const [command, ...args] = override ? override.split(/\s+/) : ["runeya"];
6134
+ const envPart = home ? { env: { RUNEYA_HOME: home } } : {};
6135
+ if (platform === "win32" && command !== "node" && !/\.exe$/i.test(command)) {
6136
+ return { command: "cmd", args: ["/c", command, ...args, "mcp"], ...envPart };
6137
+ }
6138
+ return { command, args: [...args, "mcp"], ...envPart };
6139
+ }
6140
+ function envFlags(flag, entry) {
6141
+ return Object.entries(entry.env ?? {}).flatMap(([k, v]) => [flag, `${k}=${v}`]);
6142
+ }
6143
+ function vscodeUserDir(home) {
6144
+ if (process.platform === "darwin") return join7(home, "Library", "Application Support", "Code", "User");
6145
+ if (process.platform === "win32") return join7(process.env["APPDATA"] ?? join7(home, "AppData", "Roaming"), "Code", "User");
6146
+ return join7(home, ".config", "Code", "User");
6147
+ }
6148
+ function claudeDesktopDir(home) {
6149
+ if (process.platform === "darwin") return join7(home, "Library", "Application Support", "Claude");
6150
+ if (process.platform === "win32") return join7(process.env["APPDATA"] ?? join7(home, "AppData", "Roaming"), "Claude");
6151
+ return join7(home, ".config", "Claude");
6152
+ }
6153
+ var AI_CLIENTS = [
6154
+ {
6155
+ id: "claude-code",
6156
+ label: "Claude Code",
6157
+ kind: "cli",
6158
+ binary: "claude",
6159
+ configFile: (home) => join7(home, ".claude.json"),
6160
+ isInstalled: (content) => {
6161
+ try {
6162
+ const servers2 = JSON.parse(content).mcpServers;
6163
+ return Boolean(servers2 && SERVER_NAME in servers2);
6164
+ } catch {
6165
+ return false;
6166
+ }
6167
+ },
6168
+ // `-e` prend plusieurs valeurs : placé avant le nom, il l'avalerait. `--` l'arrête.
6169
+ addArgs: (e) => ["mcp", "add", "--scope", "user", SERVER_NAME, ...envFlags("-e", e), "--", e.command, ...e.args],
6170
+ removeArgs: ["mcp", "remove", "--scope", "user", SERVER_NAME]
6171
+ },
6172
+ {
6173
+ id: "codex",
6174
+ label: "Codex",
6175
+ kind: "cli",
6176
+ binary: "codex",
6177
+ configFile: (home) => join7(home, ".codex", "config.toml"),
6178
+ isInstalled: (content) => new RegExp(`^\\s*\\[mcp_servers\\.${SERVER_NAME}\\]`, "m").test(content),
6179
+ addArgs: (e) => ["mcp", "add", ...envFlags("--env", e), SERVER_NAME, "--", e.command, ...e.args],
6180
+ removeArgs: ["mcp", "remove", SERVER_NAME]
6181
+ },
6182
+ { id: "cursor", label: "Cursor", kind: "json", detectDir: (h) => join7(h, ".cursor"), configFile: (h) => join7(h, ".cursor", "mcp.json") },
6183
+ { id: "gemini", label: "Gemini CLI", kind: "json", detectDir: (h) => join7(h, ".gemini"), configFile: (h) => join7(h, ".gemini", "settings.json") },
6184
+ {
6185
+ id: "windsurf",
6186
+ label: "Windsurf",
6187
+ kind: "json",
6188
+ detectDir: (h) => join7(h, ".codeium", "windsurf"),
6189
+ configFile: (h) => join7(h, ".codeium", "windsurf", "mcp_config.json")
6190
+ },
6191
+ {
6192
+ id: "claude-desktop",
6193
+ label: "Claude Desktop",
6194
+ kind: "json",
6195
+ detectDir: claudeDesktopDir,
6196
+ configFile: (h) => join7(claudeDesktopDir(h), "claude_desktop_config.json")
6197
+ },
6198
+ {
6199
+ id: "vscode",
6200
+ label: "VS Code",
6201
+ kind: "json",
6202
+ detectDir: vscodeUserDir,
6203
+ configFile: (h) => join7(vscodeUserDir(h), "mcp.json"),
6204
+ serversKey: "servers",
6205
+ entryFields: { type: "stdio" }
6206
+ }
6207
+ ];
6208
+ async function readText(path) {
6209
+ try {
6210
+ return await readFile4(path, "utf8");
6211
+ } catch {
6212
+ return null;
6213
+ }
6214
+ }
6215
+ async function readJsonConfig(path) {
6216
+ const text = await readText(path);
6217
+ if (text === null || text.trim() === "") return {};
6218
+ const parsed = JSON.parse(text);
6219
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
6220
+ throw new Error(`${path} ne contient pas un objet JSON.`);
6221
+ }
6222
+ return parsed;
6223
+ }
6224
+ function servers(config, key = "mcpServers") {
6225
+ const value = config[key];
6226
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
6227
+ }
6228
+ async function setJsonEntry(path, present, entry = mcpEntry(), key = "mcpServers", entryFields = {}) {
6229
+ const config = await readJsonConfig(path);
6230
+ const current2 = config[key];
6231
+ if (current2 !== void 0 && (!current2 || typeof current2 !== "object" || Array.isArray(current2))) {
6232
+ throw new Error(`${path} : \xAB ${key} \xBB n'est pas un objet, le fichier n'est pas modifi\xE9.`);
6233
+ }
6234
+ const next = { ...servers(config, key) };
6235
+ if (present) next[SERVER_NAME] = { ...entryFields, ...entry };
6236
+ else delete next[SERVER_NAME];
6237
+ const target = await realpath(path).catch(() => path);
6238
+ await mkdir5(dirname4(target), { recursive: true });
6239
+ await atomicWriteSecure(target, `${JSON.stringify({ ...config, [key]: next }, null, 2)}
6240
+ `);
6241
+ }
6242
+ function run(binary, args) {
6243
+ const { command, args: fullArgs } = resolveCliInvocation(binary, args);
6244
+ return new Promise((resolve5, reject) => {
6245
+ execFile4(command, fullArgs, { timeout: 2e4, windowsHide: true }, (err, _stdout, stderr) => {
6246
+ if (err) reject(new Error(`${binary} ${args.join(" ")} : ${String(stderr).trim() || err.message}`));
6247
+ else resolve5();
6248
+ });
6249
+ });
6250
+ }
6251
+ async function isRuneyaMcpAvailable(entry = mcpEntry()) {
6252
+ const { command, args } = resolveCliInvocation(entry.command, [...entry.args.slice(0, -1), "--help"]);
6253
+ return new Promise((resolve5) => {
6254
+ execFile4(command, args, { timeout: 1e4, windowsHide: true }, (err, stdout) => {
6255
+ resolve5(!err && /^\s+mcp\s/m.test(String(stdout)));
6256
+ });
6257
+ });
6258
+ }
6259
+ async function isDetected(client, home) {
6260
+ if (client.kind === "cli") return isCliAvailable(client.binary);
6261
+ return existsSync2(client.detectDir(home));
6262
+ }
6263
+ async function isInstalled(client, home) {
6264
+ if (client.kind === "cli") {
6265
+ const content = await readText(client.configFile(home));
6266
+ return content !== null && client.isInstalled(content);
6267
+ }
6268
+ try {
6269
+ return SERVER_NAME in servers(await readJsonConfig(client.configFile(home)), client.serversKey);
6270
+ } catch {
6271
+ return false;
6272
+ }
6273
+ }
6274
+ async function listAiClients(home = homedir3()) {
6275
+ const [runeyaMcpAvailable, statuses] = await Promise.all([
6276
+ isRuneyaMcpAvailable(),
6277
+ Promise.all(AI_CLIENTS.map(async (client) => {
6278
+ if (!await isDetected(client, home)) return null;
6279
+ return { id: client.id, label: client.label, installed: await isInstalled(client, home), configFile: client.configFile(home) };
6280
+ }))
6281
+ ]);
6282
+ return { runeyaMcpAvailable, clients: statuses.filter((s) => s !== null) };
6283
+ }
6284
+ async function setAiClientMcp(id, present, home = homedir3()) {
6285
+ const client = AI_CLIENTS.find((c) => c.id === id);
6286
+ if (!client) throw new Error(`Outil IA inconnu : ${id}`);
6287
+ if (present && !await isRuneyaMcpAvailable()) {
6288
+ const { command, args } = mcpEntry();
6289
+ throw new Error(`La commande \xAB ${[command, ...args].join(" ")} \xBB est introuvable ou ne conna\xEEt pas \xAB mcp \xBB : installez une version de Runeya qui la fournit.`);
6290
+ }
6291
+ if (client.kind === "json") return setJsonEntry(client.configFile(home), present, mcpEntry(), client.serversKey, client.entryFields);
6292
+ if (await isInstalled(client, home)) await run(client.binary, client.removeArgs);
6293
+ if (present) await run(client.binary, client.addArgs(mcpEntry()));
6294
+ }
6295
+
6296
+ // ../server/src/trpc/routers/ai-clients.ts
6297
+ var AiClientIdSchema = z21.enum(["claude-code", "codex", "cursor", "gemini", "windsurf", "claude-desktop", "vscode"]);
6298
+ var aiClientsRouter = router({
6299
+ list: protectedProcedure.query(({ ctx }) => {
6300
+ requireHuman(ctx);
6301
+ return listAiClients();
6302
+ }),
6303
+ setInstalled: protectedProcedure.input(z21.object({ id: AiClientIdSchema, installed: z21.boolean() })).mutation(async ({ ctx, input }) => {
6304
+ requireHuman(ctx);
6305
+ try {
6306
+ await setAiClientMcp(input.id, input.installed);
6307
+ } catch (err) {
6308
+ throw new TRPCError18({ code: "BAD_REQUEST", message: err.message });
6309
+ }
6310
+ return listAiClients();
6311
+ })
6312
+ });
6313
+
6314
+ // ../server/src/trpc/routers/kanban-queue.ts
6315
+ import { z as z22 } from "zod";
5822
6316
 
5823
6317
  // ../server/src/services/kanban-queue.ts
5824
- import { randomUUID as randomUUID5 } from "crypto";
6318
+ import { randomUUID as randomUUID6 } from "crypto";
5825
6319
  import { EventEmitter as EventEmitter2 } from "events";
5826
6320
  var KanbanQueueService = class extends EventEmitter2 {
5827
6321
  items = [];
@@ -5849,7 +6343,7 @@ var KanbanQueueService = class extends EventEmitter2 {
5849
6343
  return { id: alreadyRunning.id, addedAt: alreadyRunning.startedAt, ...item };
5850
6344
  }
5851
6345
  const queueItem = {
5852
- id: randomUUID5(),
6346
+ id: randomUUID6(),
5853
6347
  ...item,
5854
6348
  addedAt: (/* @__PURE__ */ new Date()).toISOString()
5855
6349
  };
@@ -5877,9 +6371,9 @@ var KanbanQueueService = class extends EventEmitter2 {
5877
6371
  }
5878
6372
  const runIdx = this.running.findIndex((r) => r.id === itemId);
5879
6373
  if (runIdx >= 0) {
5880
- const run = this.running[runIdx];
5881
- abortScenario(run.conversationId);
5882
- activeSessionRegistry.kill(run.conversationId);
6374
+ const run2 = this.running[runIdx];
6375
+ abortScenario(run2.conversationId);
6376
+ activeSessionRegistry.kill(run2.conversationId);
5883
6377
  this.running.splice(runIdx, 1);
5884
6378
  this.emit("changed");
5885
6379
  this.tick();
@@ -5889,9 +6383,9 @@ var KanbanQueueService = class extends EventEmitter2 {
5889
6383
  }
5890
6384
  stopAll() {
5891
6385
  this.items = [];
5892
- for (const run of this.running) {
5893
- abortScenario(run.conversationId);
5894
- activeSessionRegistry.kill(run.conversationId);
6386
+ for (const run2 of this.running) {
6387
+ abortScenario(run2.conversationId);
6388
+ activeSessionRegistry.kill(run2.conversationId);
5895
6389
  }
5896
6390
  this.running = [];
5897
6391
  this.emit("changed");
@@ -6010,50 +6504,50 @@ var kanbanQueue = new KanbanQueueService();
6010
6504
  // ../server/src/trpc/routers/kanban-queue.ts
6011
6505
  var kanbanQueueRouter = router({
6012
6506
  getState: protectedProcedure.query(() => kanbanQueue.getState()),
6013
- add: protectedProcedure.input(z19.object({
6014
- boardId: z19.string().max(128),
6015
- cardId: z19.string().max(128),
6016
- runner: z19.enum(["claude-code", "codex"]).optional(),
6017
- model: z19.string().max(128).optional(),
6018
- scenarioId: z19.string().max(128).optional(),
6507
+ add: protectedProcedure.input(z22.object({
6508
+ boardId: z22.string().max(128),
6509
+ cardId: z22.string().max(128),
6510
+ runner: z22.enum(["claude-code", "codex"]).optional(),
6511
+ model: z22.string().max(128).optional(),
6512
+ scenarioId: z22.string().max(128).optional(),
6019
6513
  // Le projet ouvert quand la carte a été mise en file : la conversation
6020
6514
  // qui en naîtra lui appartient, plutôt que de rester orpheline et de
6021
6515
  // s'afficher dans tous les projets.
6022
- projectId: z19.string().max(128).optional(),
6023
- orgId: z19.string().max(128).nullable().optional()
6516
+ projectId: z22.string().max(128).optional(),
6517
+ orgId: z22.string().max(128).nullable().optional()
6024
6518
  })).mutation(({ input }) => kanbanQueue.add(input)),
6025
- addColumn: protectedProcedure.input(z19.object({
6026
- boardId: z19.string().max(128),
6027
- columnId: z19.string().max(128),
6519
+ addColumn: protectedProcedure.input(z22.object({
6520
+ boardId: z22.string().max(128),
6521
+ columnId: z22.string().max(128),
6028
6522
  // Les cartes visibles côté écran : une colonne filtrée n'envoie qu'elles.
6029
- cardIds: z19.array(z19.string().max(128)).max(5e3).optional(),
6030
- runner: z19.enum(["claude-code", "codex"]).optional(),
6031
- model: z19.string().max(128).optional(),
6032
- scenarioId: z19.string().max(128).optional(),
6033
- projectId: z19.string().max(128).optional(),
6034
- orgId: z19.string().max(128).nullable().optional()
6523
+ cardIds: z22.array(z22.string().max(128)).max(5e3).optional(),
6524
+ runner: z22.enum(["claude-code", "codex"]).optional(),
6525
+ model: z22.string().max(128).optional(),
6526
+ scenarioId: z22.string().max(128).optional(),
6527
+ projectId: z22.string().max(128).optional(),
6528
+ orgId: z22.string().max(128).nullable().optional()
6035
6529
  })).mutation(async ({ input }) => {
6036
6530
  const { boardId, columnId, ...options } = input;
6037
6531
  return kanbanQueue.addColumn(boardId, columnId, options);
6038
6532
  }),
6039
- remove: protectedProcedure.input(z19.object({ itemId: z19.string().max(128) })).mutation(({ input }) => ({ removed: kanbanQueue.remove(input.itemId) })),
6533
+ remove: protectedProcedure.input(z22.object({ itemId: z22.string().max(128) })).mutation(({ input }) => ({ removed: kanbanQueue.remove(input.itemId) })),
6040
6534
  stopAll: protectedProcedure.mutation(() => {
6041
6535
  kanbanQueue.stopAll();
6042
6536
  return { ok: true };
6043
6537
  }),
6044
- setMaxConcurrent: protectedProcedure.input(z19.object({ max: z19.number().int().min(1).max(20) })).mutation(({ input }) => {
6538
+ setMaxConcurrent: protectedProcedure.input(z22.object({ max: z22.number().int().min(1).max(20) })).mutation(({ input }) => {
6045
6539
  kanbanQueue.setMaxConcurrent(input.max);
6046
6540
  return kanbanQueue.getState();
6047
6541
  }),
6048
6542
  onChanged: protectedProcedure.subscription(async function* () {
6049
6543
  const queue = [];
6050
- let resolve4 = null;
6544
+ let resolve5 = null;
6051
6545
  const handler = () => {
6052
6546
  if (queue.length >= 50) queue.shift();
6053
6547
  queue.push(kanbanQueue.getState());
6054
- if (resolve4) {
6055
- resolve4();
6056
- resolve4 = null;
6548
+ if (resolve5) {
6549
+ resolve5();
6550
+ resolve5 = null;
6057
6551
  }
6058
6552
  };
6059
6553
  kanbanQueue.on("changed", handler);
@@ -6064,7 +6558,7 @@ var kanbanQueueRouter = router({
6064
6558
  yield queue.shift();
6065
6559
  } else {
6066
6560
  await new Promise((r) => {
6067
- resolve4 = r;
6561
+ resolve5 = r;
6068
6562
  });
6069
6563
  }
6070
6564
  }
@@ -6075,12 +6569,12 @@ var kanbanQueueRouter = router({
6075
6569
  });
6076
6570
 
6077
6571
  // ../server/src/trpc/routers/kanban.ts
6078
- import { mkdir as mkdir5, readdir, rm, stat as stat3, writeFile as writeFile4 } from "fs/promises";
6079
- import { createHash } from "crypto";
6572
+ import { mkdir as mkdir6, readdir, rm, stat as stat3, writeFile as writeFile4 } from "fs/promises";
6573
+ import { createHash as createHash2 } from "crypto";
6080
6574
  import { tmpdir } from "os";
6081
- import { join as join7 } from "path";
6082
- import { z as z20 } from "zod";
6083
- import { TRPCError as TRPCError17 } from "@trpc/server";
6575
+ import { join as join8 } from "path";
6576
+ import { z as z23 } from "zod";
6577
+ import { TRPCError as TRPCError19 } from "@trpc/server";
6084
6578
  function summarizeCard(card, boardId, columnTitle) {
6085
6579
  return {
6086
6580
  boardId,
@@ -6121,14 +6615,14 @@ function safeDecode(value) {
6121
6615
  }
6122
6616
  var MAX_ATTACHMENT_BYTES = 55 * 1024 * 1024;
6123
6617
  function attachmentCacheDir() {
6124
- return join7(tmpdir(), "runeya-kanban-attachments");
6618
+ return join8(tmpdir(), "runeya-kanban-attachments");
6125
6619
  }
6126
6620
  var ATTACHMENT_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
6127
6621
  async function purgeStaleAttachments(dir, ttlMs = ATTACHMENT_CACHE_TTL_MS) {
6128
6622
  try {
6129
6623
  const cutoff = Date.now() - ttlMs;
6130
6624
  for (const name of await readdir(dir)) {
6131
- const path = join7(dir, name);
6625
+ const path = join8(dir, name);
6132
6626
  const info = await stat3(path).catch(() => null);
6133
6627
  if (info && info.mtimeMs < cutoff) await rm(path, { force: true });
6134
6628
  }
@@ -6136,14 +6630,14 @@ async function purgeStaleAttachments(dir, ttlMs = ATTACHMENT_CACHE_TTL_MS) {
6136
6630
  }
6137
6631
  }
6138
6632
  function attachmentCacheDirs() {
6139
- return [attachmentCacheDir(), join7(machineRoot(), "kanban-attachments")];
6633
+ return [attachmentCacheDir(), join8(machineRoot(), "kanban-attachments")];
6140
6634
  }
6141
6635
  async function storeAttachment(id, name, body, dirs = attachmentCacheDirs()) {
6142
6636
  const refusals = [];
6143
6637
  for (const dir of dirs) {
6144
6638
  try {
6145
- await mkdir5(dir, { recursive: true });
6146
- const path = join7(dir, safeLocalName(id, name));
6639
+ await mkdir6(dir, { recursive: true });
6640
+ const path = join8(dir, safeLocalName(id, name));
6147
6641
  await writeFile4(path, body);
6148
6642
  void purgeStaleAttachments(dir);
6149
6643
  return path;
@@ -6151,13 +6645,13 @@ async function storeAttachment(id, name, body, dirs = attachmentCacheDirs()) {
6151
6645
  refusals.push(`${dir} (${err instanceof Error ? err.message : String(err)})`);
6152
6646
  }
6153
6647
  }
6154
- throw new TRPCError17({
6648
+ throw new TRPCError19({
6155
6649
  code: "INTERNAL_SERVER_ERROR",
6156
6650
  message: `Impossible d'\xE9crire la pi\xE8ce jointe sur cette machine. Emplacements essay\xE9s : ${refusals.join(" ; ")}. V\xE9rifiez les droits d'\xE9criture, ou l'espace disque.`
6157
6651
  });
6158
6652
  }
6159
6653
  function safeLocalName(id, name) {
6160
- const base = createHash("sha256").update(id).digest("hex").slice(0, 32);
6654
+ const base = createHash2("sha256").update(id).digest("hex").slice(0, 32);
6161
6655
  const ext = /\.([A-Za-z0-9]{1,12})$/.exec(name)?.[1];
6162
6656
  return ext ? `${base}.${ext.toLowerCase()}` : base;
6163
6657
  }
@@ -6172,9 +6666,9 @@ var kanbanRouter = router({
6172
6666
  * card graph, so listing them in full would be megabytes to answer "which
6173
6667
  * boards exist?". Use `getBoard` for the cards of one.
6174
6668
  */
6175
- listBoards: protectedProcedure.input(z20.object({
6176
- includeArchived: z20.boolean().optional(),
6177
- organizationId: z20.string().max(128).optional()
6669
+ listBoards: protectedProcedure.input(z23.object({
6670
+ includeArchived: z23.boolean().optional(),
6671
+ organizationId: z23.string().max(128).optional()
6178
6672
  }).optional()).query(async ({ input }) => {
6179
6673
  const { organizationId, boards } = await kanbanStore.list(
6180
6674
  input?.includeArchived ?? false,
@@ -6200,7 +6694,7 @@ var kanbanRouter = router({
6200
6694
  };
6201
6695
  }),
6202
6696
  /** One board: its columns, and the cards in each. */
6203
- getBoard: protectedProcedure.input(z20.object({ boardId: z20.string().max(128) })).query(async ({ input }) => {
6697
+ getBoard: protectedProcedure.input(z23.object({ boardId: z23.string().max(128) })).query(async ({ input }) => {
6204
6698
  const board = await kanbanStore.get(input.boardId);
6205
6699
  if (!board) return { board: null };
6206
6700
  const { links } = await kanbanStore.cardLinks(input.boardId);
@@ -6239,12 +6733,12 @@ var kanbanRouter = router({
6239
6733
  * organization — or one board when `boardId` is given. Archived boards are
6240
6734
  * left out: they are what the team decided to stop looking at.
6241
6735
  */
6242
- searchCards: protectedProcedure.input(z20.object({
6243
- query: z20.string().max(256).optional(),
6244
- createdBy: z20.string().min(1).max(200).optional(),
6245
- boardId: z20.string().max(128).optional(),
6246
- limit: z20.number().int().min(1).max(100).optional(),
6247
- organizationId: z20.string().max(128).optional()
6736
+ searchCards: protectedProcedure.input(z23.object({
6737
+ query: z23.string().max(256).optional(),
6738
+ createdBy: z23.string().min(1).max(200).optional(),
6739
+ boardId: z23.string().max(128).optional(),
6740
+ limit: z23.number().int().min(1).max(100).optional(),
6741
+ organizationId: z23.string().max(128).optional()
6248
6742
  }).refine((i) => !!i.query?.trim() || !!i.createdBy?.trim(), { message: "query or createdBy is required." })).query(async ({ input }) => {
6249
6743
  return kanbanStore.searchCards(input);
6250
6744
  }),
@@ -6252,10 +6746,10 @@ var kanbanRouter = router({
6252
6746
  * Les conversations de support liées à une carte, avec leur fil — ce que
6253
6747
  * le client a écrit à l'équipe, séparé des commentaires de la carte.
6254
6748
  */
6255
- getCardConversations: protectedProcedure.input(z20.object({
6256
- boardId: z20.string().max(128),
6257
- cardId: z20.string().max(128),
6258
- limit: z20.number().int().min(1).max(500).optional()
6749
+ getCardConversations: protectedProcedure.input(z23.object({
6750
+ boardId: z23.string().max(128),
6751
+ cardId: z23.string().max(128),
6752
+ limit: z23.number().int().min(1).max(500).optional()
6259
6753
  })).query(async ({ input }) => {
6260
6754
  const conversations = await kanbanStore.cardConversations(input.boardId, input.cardId);
6261
6755
  const limit = input.limit ?? 100;
@@ -6268,13 +6762,13 @@ var kanbanRouter = router({
6268
6762
  };
6269
6763
  }),
6270
6764
  /** Chercher une conversation chez les fournisseurs de l'organisation (lecture seule côté fournisseur). */
6271
- searchConversations: protectedProcedure.input(z20.object({ boardId: z20.string().max(128), query: z20.string().trim().min(1).max(500) })).query(async ({ input }) => ({ results: await kanbanStore.searchConversations(input.boardId, input.query) })),
6765
+ searchConversations: protectedProcedure.input(z23.object({ boardId: z23.string().max(128), query: z23.string().trim().min(1).max(500) })).query(async ({ input }) => ({ results: await kanbanStore.searchConversations(input.boardId, input.query) })),
6272
6766
  /** Lier une conversation trouvée à une carte. N'écrit rien chez le fournisseur. */
6273
- linkConversation: protectedProcedure.input(z20.object({
6274
- boardId: z20.string().max(128),
6275
- cardId: z20.string().max(128),
6276
- connectionId: z20.string().max(128),
6277
- externalId: z20.string().max(128)
6767
+ linkConversation: protectedProcedure.input(z23.object({
6768
+ boardId: z23.string().max(128),
6769
+ cardId: z23.string().max(128),
6770
+ connectionId: z23.string().max(128),
6771
+ externalId: z23.string().max(128)
6278
6772
  })).mutation(async ({ input }) => ({ conversations: await kanbanStore.linkConversation(input) })),
6279
6773
  /**
6280
6774
  * Le fil de commentaires d'une carte, relu à la demande.
@@ -6285,10 +6779,10 @@ var kanbanRouter = router({
6285
6779
  * qu'un `commentCount`, et rien n'en donnait le texte. Cette route est la
6286
6780
  * relecture, pour une seule carte : c'est le journal qui pèse, pas le reste.
6287
6781
  */
6288
- getCardComments: protectedProcedure.input(z20.object({
6289
- boardId: z20.string().max(128),
6290
- cardId: z20.string().max(128),
6291
- limit: z20.number().int().min(1).max(200).optional()
6782
+ getCardComments: protectedProcedure.input(z23.object({
6783
+ boardId: z23.string().max(128),
6784
+ cardId: z23.string().max(128),
6785
+ limit: z23.number().int().min(1).max(200).optional()
6292
6786
  })).query(async ({ input }) => {
6293
6787
  const history = await kanbanStore.cardHistory(input.boardId, input.cardId);
6294
6788
  const comments = history.filter((e) => e.type === "comment");
@@ -6317,18 +6811,18 @@ var kanbanRouter = router({
6317
6811
  * `getBoard` en donne déjà l'essentiel carte par carte ; cette route sert
6318
6812
  * quand c'est l'arbre lui-même qu'on veut, dates et auteurs compris.
6319
6813
  */
6320
- getCardLinks: protectedProcedure.input(z20.object({ boardId: z20.string().max(128) })).query(async ({ input }) => kanbanStore.cardLinks(input.boardId)),
6814
+ getCardLinks: protectedProcedure.input(z23.object({ boardId: z23.string().max(128) })).query(async ({ input }) => kanbanStore.cardLinks(input.boardId)),
6321
6815
  /**
6322
6816
  * Rattacher une carte à une carte parente, ou l'en détacher.
6323
6817
  *
6324
6818
  * Sans `parentCardId`, le lien est retiré. Le parent peut vivre sur un autre
6325
6819
  * tableau : `parentBoardId` le désigne alors, sinon c'est celui de la carte.
6326
6820
  */
6327
- setCardParent: protectedProcedure.input(z20.object({
6328
- boardId: z20.string().max(128),
6329
- cardId: z20.string().max(128),
6330
- parentCardId: z20.string().max(128).optional(),
6331
- parentBoardId: z20.string().max(128).optional()
6821
+ setCardParent: protectedProcedure.input(z23.object({
6822
+ boardId: z23.string().max(128),
6823
+ cardId: z23.string().max(128),
6824
+ parentCardId: z23.string().max(128).optional(),
6825
+ parentBoardId: z23.string().max(128).optional()
6332
6826
  })).mutation(async ({ input }) => {
6333
6827
  const ok = await kanbanStore.setCardParent(
6334
6828
  input.boardId,
@@ -6336,7 +6830,7 @@ var kanbanRouter = router({
6336
6830
  input.parentCardId ? { boardId: input.parentBoardId ?? input.boardId, cardId: input.parentCardId } : null
6337
6831
  );
6338
6832
  if (!ok) {
6339
- throw new TRPCError17({ code: "BAD_REQUEST", message: "The cloud refused the link (unknown card, or a cycle)." });
6833
+ throw new TRPCError19({ code: "BAD_REQUEST", message: "The cloud refused the link (unknown card, or a cycle)." });
6340
6834
  }
6341
6835
  return { ok: true };
6342
6836
  }),
@@ -6345,48 +6839,48 @@ var kanbanRouter = router({
6345
6839
  * `boardId` est le tableau où on la pose (ou d'où on la retire) ; la carte
6346
6840
  * reste sur son tableau d'origine.
6347
6841
  */
6348
- getCardPlacements: protectedProcedure.input(z20.object({ boardId: z20.string().max(128), cardId: z20.string().max(128) })).query(async ({ input }) => ({ placements: await kanbanStore.cardPlacements(input.boardId, input.cardId) })),
6349
- placeCard: protectedProcedure.input(z20.object({ boardId: z20.string().max(128), cardId: z20.string().max(128), column: z20.string().min(1).max(200) })).mutation(async ({ input }) => {
6842
+ getCardPlacements: protectedProcedure.input(z23.object({ boardId: z23.string().max(128), cardId: z23.string().max(128) })).query(async ({ input }) => ({ placements: await kanbanStore.cardPlacements(input.boardId, input.cardId) })),
6843
+ placeCard: protectedProcedure.input(z23.object({ boardId: z23.string().max(128), cardId: z23.string().max(128), column: z23.string().min(1).max(200) })).mutation(async ({ input }) => {
6350
6844
  const res = await kanbanStore.placeCard(input.boardId, input.cardId, input.column);
6351
6845
  if (!res.ok) {
6352
- throw new TRPCError17({
6846
+ throw new TRPCError19({
6353
6847
  code: "BAD_REQUEST",
6354
6848
  message: res.reason ?? "The cloud refused the placement (unknown card or column, archived card, or already on this board)."
6355
6849
  });
6356
6850
  }
6357
6851
  return { ok: true };
6358
6852
  }),
6359
- unplaceCard: protectedProcedure.input(z20.object({ boardId: z20.string().max(128), cardId: z20.string().max(128) })).mutation(async ({ input }) => {
6853
+ unplaceCard: protectedProcedure.input(z23.object({ boardId: z23.string().max(128), cardId: z23.string().max(128) })).mutation(async ({ input }) => {
6360
6854
  if (!await kanbanStore.unplaceCard(input.boardId, input.cardId)) {
6361
- throw new TRPCError17({ code: "NOT_FOUND", message: "This card is not placed on this board." });
6855
+ throw new TRPCError19({ code: "NOT_FOUND", message: "This card is not placed on this board." });
6362
6856
  }
6363
6857
  return { ok: true };
6364
6858
  }),
6365
- moveCard: protectedProcedure.input(z20.object({
6366
- boardId: z20.string().max(128),
6367
- cardId: z20.string().max(128),
6368
- targetColumn: z20.string().max(256)
6859
+ moveCard: protectedProcedure.input(z23.object({
6860
+ boardId: z23.string().max(128),
6861
+ cardId: z23.string().max(128),
6862
+ targetColumn: z23.string().max(256)
6369
6863
  })).mutation(async ({ input }) => {
6370
6864
  await executeKanbanCommands(input.boardId, input.cardId, [
6371
6865
  { type: "move_card", columnTitle: input.targetColumn }
6372
6866
  ]);
6373
6867
  return { ok: true };
6374
6868
  }),
6375
- addComment: protectedProcedure.input(z20.object({
6376
- boardId: z20.string().max(128),
6377
- cardId: z20.string().max(128),
6378
- comment: z20.string().max(1e5)
6869
+ addComment: protectedProcedure.input(z23.object({
6870
+ boardId: z23.string().max(128),
6871
+ cardId: z23.string().max(128),
6872
+ comment: z23.string().max(1e5)
6379
6873
  })).mutation(async ({ input }) => {
6380
6874
  await executeKanbanCommands(input.boardId, input.cardId, [
6381
6875
  { type: "add_comment", comment: input.comment }
6382
6876
  ]);
6383
6877
  return { ok: true };
6384
6878
  }),
6385
- addCard: protectedProcedure.input(z20.object({
6386
- boardId: z20.string().max(128),
6387
- column: z20.string().max(256),
6388
- title: z20.string().max(512),
6389
- content: z20.string().max(2e4).optional()
6879
+ addCard: protectedProcedure.input(z23.object({
6880
+ boardId: z23.string().max(128),
6881
+ column: z23.string().max(256),
6882
+ title: z23.string().max(512),
6883
+ content: z23.string().max(2e4).optional()
6390
6884
  })).mutation(async ({ input }) => {
6391
6885
  await executeKanbanBoardCommands(input.boardId, [
6392
6886
  { type: "add_card", columnTitle: input.column, cardTitle: input.title, cardContent: input.content }
@@ -6399,11 +6893,34 @@ var kanbanRouter = router({
6399
6893
  * `getBoard` rapatrie le tableau entier pour n'en regarder qu'une ; et il ne
6400
6894
  * porte pas les cartes rangées. Une carte s'adresse par son identifiant.
6401
6895
  */
6402
- getCard: protectedProcedure.input(z20.object({ boardId: z20.string().max(128), cardId: z20.string().max(128) })).query(async ({ input }) => {
6896
+ getCard: protectedProcedure.input(z23.object({ boardId: z23.string().max(128), cardId: z23.string().max(128) })).query(async ({ input }) => {
6403
6897
  const found = await kanbanStore.card(input.boardId, input.cardId);
6404
6898
  if (!found) throw new Error(`Carte ${input.cardId} introuvable sur le tableau ${input.boardId}.`);
6405
6899
  return { ...summarizeCard(found.card, input.boardId, ""), columnId: found.columnId };
6406
6900
  }),
6901
+ /**
6902
+ * Le contexte d'une carte pour un outil IA lancé hors de Runeya : le même
6903
+ * texte qu'une session lancée depuis la carte reçoit, construit au même
6904
+ * endroit (`buildKanbanCardPrompt`). L'outil le reçoit comme un message, pas
6905
+ * comme une consigne système : il guide le travail sans en avoir la force.
6906
+ */
6907
+ cardContext: protectedProcedure.input(z23.object({
6908
+ boardId: z23.string().max(128),
6909
+ cardId: z23.string().max(128),
6910
+ projectId: z23.string().max(128).optional(),
6911
+ orgId: z23.string().max(128).optional()
6912
+ })).query(async ({ input }) => {
6913
+ const board = await kanbanStore.get(input.boardId);
6914
+ if (!board) throw new TRPCError19({ code: "NOT_FOUND", message: `Tableau ${input.boardId} introuvable.` });
6915
+ if (!board.columns.some((col) => col.cards.some((c) => c.id === input.cardId))) {
6916
+ throw new TRPCError19({ code: "NOT_FOUND", message: `Carte ${input.cardId} introuvable sur le tableau ${input.boardId}.` });
6917
+ }
6918
+ const scope = {
6919
+ ...input.projectId ? { projectId: input.projectId } : {},
6920
+ ...input.orgId ? { orgId: input.orgId } : {}
6921
+ };
6922
+ return { context: await buildKanbanCardPrompt(board, input.cardId, scope) };
6923
+ }),
6407
6924
  /**
6408
6925
  * Modifier une carte.
6409
6926
  *
@@ -6413,16 +6930,16 @@ var kanbanRouter = router({
6413
6930
  * Un champ absent n'est pas touché ; `null` l'efface. La distinction compte —
6414
6931
  * changer un titre ne doit pas vider une couleur.
6415
6932
  */
6416
- updateCard: protectedProcedure.input(z20.object({
6417
- boardId: z20.string().max(128),
6418
- cardId: z20.string().max(128),
6419
- title: z20.string().min(1).max(512).optional(),
6420
- content: z20.string().max(1e5).optional(),
6421
- priority: z20.number().int().min(1).max(10).nullable().optional(),
6422
- color: z20.string().max(32).nullable().optional(),
6423
- groups: z20.array(z20.string().min(1).max(60)).max(30).optional(),
6424
- deadline: z20.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().optional(),
6425
- assignees: z20.array(z20.string().max(128)).max(50).optional()
6933
+ updateCard: protectedProcedure.input(z23.object({
6934
+ boardId: z23.string().max(128),
6935
+ cardId: z23.string().max(128),
6936
+ title: z23.string().min(1).max(512).optional(),
6937
+ content: z23.string().max(1e5).optional(),
6938
+ priority: z23.number().int().min(1).max(10).nullable().optional(),
6939
+ color: z23.string().max(32).nullable().optional(),
6940
+ groups: z23.array(z23.string().min(1).max(60)).max(30).optional(),
6941
+ deadline: z23.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().optional(),
6942
+ assignees: z23.array(z23.string().max(128)).max(50).optional()
6426
6943
  })).mutation(async ({ input }) => {
6427
6944
  const { boardId, cardId, content, ...rest } = input;
6428
6945
  const patch = { ...rest };
@@ -6433,10 +6950,10 @@ var kanbanRouter = router({
6433
6950
  return summarizeCard(card, boardId, "");
6434
6951
  }),
6435
6952
  /** Ranger une carte, ou la reprendre. */
6436
- setCardArchived: protectedProcedure.input(z20.object({
6437
- boardId: z20.string().max(128),
6438
- cardId: z20.string().max(128),
6439
- archived: z20.boolean()
6953
+ setCardArchived: protectedProcedure.input(z23.object({
6954
+ boardId: z23.string().max(128),
6955
+ cardId: z23.string().max(128),
6956
+ archived: z23.boolean()
6440
6957
  })).mutation(async ({ input }) => {
6441
6958
  const ok = await kanbanStore.setCardArchived(input.boardId, input.cardId, input.archived);
6442
6959
  if (!ok) throw new Error(`La carte ${input.cardId} n'a pas pu \xEAtre ${input.archived ? "archiv\xE9e" : "reprise"}.`);
@@ -6448,48 +6965,48 @@ var kanbanRouter = router({
6448
6965
  * Sans cette liste, assigner revient à deviner un identifiant — et l'agent
6449
6966
  * n'a aucun moyen de le trouver.
6450
6967
  */
6451
- listMembers: protectedProcedure.input(z20.object({ organizationId: z20.string().max(128).optional() })).query(async ({ input }) => ({ members: await kanbanStore.members(input.organizationId) })),
6968
+ listMembers: protectedProcedure.input(z23.object({ organizationId: z23.string().max(128).optional() })).query(async ({ input }) => ({ members: await kanbanStore.members(input.organizationId) })),
6452
6969
  /**
6453
6970
  * Fetch one card attachment from the cloud and drop it on this machine, so an
6454
6971
  * agent can open it with its own file tools. The bytes never go through the
6455
6972
  * prompt: the card context lists the attachments, this returns a path.
6456
6973
  */
6457
- downloadAttachment: protectedProcedure.input(z20.object({
6458
- attachmentId: z20.string().max(128),
6974
+ downloadAttachment: protectedProcedure.input(z23.object({
6975
+ attachmentId: z23.string().max(128),
6459
6976
  // La pièce jointe appartient à une organisation : la nommer dit sous quel
6460
6977
  // compte la demander.
6461
- orgId: z20.string().max(128).optional(),
6978
+ orgId: z23.string().max(128).optional(),
6462
6979
  // À défaut, le tableau d'où vient la carte suffit : il désigne un compte
6463
6980
  // comme le fait n'importe quel autre appel du kanban. Sans lui, la route
6464
6981
  // retombait sur le compte unique du poste et refusait de servir dès qu'il
6465
6982
  // y en avait plusieurs — alors que l'agent, qui n'a qu'un identifiant de
6466
6983
  // fichier, n'avait aucun moyen de nommer l'organisation.
6467
- boardId: z20.string().max(128).optional()
6984
+ boardId: z23.string().max(128).optional()
6468
6985
  })).mutation(async ({ input }) => {
6469
6986
  const id = input.attachmentId;
6470
6987
  if (!ATTACHMENT_ID_REGEX.test(id)) {
6471
- throw new TRPCError17({ code: "BAD_REQUEST", message: "Invalid attachment id" });
6988
+ throw new TRPCError19({ code: "BAD_REQUEST", message: "Invalid attachment id" });
6472
6989
  }
6473
6990
  const res = await cloudFetch2(`/api/kanban/file/${id}`, {
6474
6991
  timeoutMs: 12e4,
6475
6992
  accountId: await attachmentScope(input)
6476
6993
  });
6477
6994
  if (res.status === 404) {
6478
- throw new TRPCError17({ code: "NOT_FOUND", message: `Attachment ${id} not found` });
6995
+ throw new TRPCError19({ code: "NOT_FOUND", message: `Attachment ${id} not found` });
6479
6996
  }
6480
6997
  if (!res.ok) {
6481
- throw new TRPCError17({
6998
+ throw new TRPCError19({
6482
6999
  code: res.status === 403 ? "FORBIDDEN" : "INTERNAL_SERVER_ERROR",
6483
7000
  message: `Cloud refused the attachment (HTTP ${res.status})`
6484
7001
  });
6485
7002
  }
6486
7003
  const declared = Number(res.headers.get("content-length") ?? 0);
6487
7004
  if (declared > MAX_ATTACHMENT_BYTES) {
6488
- throw new TRPCError17({ code: "PAYLOAD_TOO_LARGE", message: "Attachment too large to download" });
7005
+ throw new TRPCError19({ code: "PAYLOAD_TOO_LARGE", message: "Attachment too large to download" });
6489
7006
  }
6490
7007
  const body = Buffer.from(await res.arrayBuffer());
6491
7008
  if (body.length > MAX_ATTACHMENT_BYTES) {
6492
- throw new TRPCError17({ code: "PAYLOAD_TOO_LARGE", message: "Attachment too large to download" });
7009
+ throw new TRPCError19({ code: "PAYLOAD_TOO_LARGE", message: "Attachment too large to download" });
6493
7010
  }
6494
7011
  const contentType = res.headers.get("content-type") ?? "application/octet-stream";
6495
7012
  const disposition = res.headers.get("content-disposition") ?? "";
@@ -6501,21 +7018,21 @@ var kanbanRouter = router({
6501
7018
  });
6502
7019
 
6503
7020
  // ../server/src/trpc/routers/terminal.ts
6504
- import { z as z21 } from "zod";
6505
- import { TRPCError as TRPCError18 } from "@trpc/server";
7021
+ import { z as z24 } from "zod";
7022
+ import { TRPCError as TRPCError20 } from "@trpc/server";
6506
7023
  var terminalRouter = router({
6507
7024
  /** Create a PTY shell session on the target agent */
6508
- create: protectedProcedure.input(z21.object({
6509
- agentId: z21.string().max(128),
6510
- cwd: z21.string().max(512).optional(),
7025
+ create: protectedProcedure.input(z24.object({
7026
+ agentId: z24.string().max(128),
7027
+ cwd: z24.string().max(512).optional(),
6511
7028
  /** Anchors the terminal when no explicit cwd is given. */
6512
- serviceId: z21.string().max(128).optional(),
6513
- projectId: z21.string().max(128).optional(),
7029
+ serviceId: z24.string().max(128).optional(),
7030
+ projectId: z24.string().max(128).optional(),
6514
7031
  /** L'organisation du projet : un id seul peut en nommer un par organisation. */
6515
- orgId: z21.string().max(128).nullable().optional(),
6516
- cols: z21.number().int().min(1).max(512).optional(),
6517
- rows: z21.number().int().min(1).max(256).optional(),
6518
- title: z21.string().max(128).optional()
7032
+ orgId: z24.string().max(128).nullable().optional(),
7033
+ cols: z24.number().int().min(1).max(512).optional(),
7034
+ rows: z24.number().int().min(1).max(256).optional(),
7035
+ title: z24.string().max(128).optional()
6519
7036
  })).mutation(async ({ input }) => {
6520
7037
  const result = await agentManager.agentMutation(
6521
7038
  input.agentId,
@@ -6536,17 +7053,17 @@ var terminalRouter = router({
6536
7053
  return result;
6537
7054
  }),
6538
7055
  /** Les sessions PTY encore vivantes sur l'agent (onglets à retrouver) */
6539
- list: protectedProcedure.input(z21.object({ agentId: z21.string().max(128) })).query(async ({ input }) => {
7056
+ list: protectedProcedure.input(z24.object({ agentId: z24.string().max(128) })).query(async ({ input }) => {
6540
7057
  return agentManager.agentQuery(
6541
7058
  input.agentId,
6542
7059
  "terminal.list"
6543
7060
  );
6544
7061
  }),
6545
7062
  /** Renommer un onglet */
6546
- rename: protectedProcedure.input(z21.object({
6547
- agentId: z21.string().max(128),
6548
- sessionId: z21.string().max(128),
6549
- title: z21.string().max(128)
7063
+ rename: protectedProcedure.input(z24.object({
7064
+ agentId: z24.string().max(128),
7065
+ sessionId: z24.string().max(128),
7066
+ title: z24.string().max(128)
6550
7067
  })).mutation(async ({ input }) => {
6551
7068
  await agentManager.agentMutation(
6552
7069
  input.agentId,
@@ -6556,10 +7073,10 @@ var terminalRouter = router({
6556
7073
  return { ok: true };
6557
7074
  }),
6558
7075
  /** Send keystrokes / data to a PTY session */
6559
- write: protectedProcedure.input(z21.object({
6560
- agentId: z21.string().max(128),
6561
- sessionId: z21.string().max(128),
6562
- data: z21.string().max(65536)
7076
+ write: protectedProcedure.input(z24.object({
7077
+ agentId: z24.string().max(128),
7078
+ sessionId: z24.string().max(128),
7079
+ data: z24.string().max(65536)
6563
7080
  })).mutation(async ({ input }) => {
6564
7081
  await agentManager.agentMutation(
6565
7082
  input.agentId,
@@ -6569,11 +7086,11 @@ var terminalRouter = router({
6569
7086
  return { ok: true };
6570
7087
  }),
6571
7088
  /** Resize the PTY */
6572
- resize: protectedProcedure.input(z21.object({
6573
- agentId: z21.string().max(128),
6574
- sessionId: z21.string().max(128),
6575
- cols: z21.number().int().min(1).max(512),
6576
- rows: z21.number().int().min(1).max(256)
7089
+ resize: protectedProcedure.input(z24.object({
7090
+ agentId: z24.string().max(128),
7091
+ sessionId: z24.string().max(128),
7092
+ cols: z24.number().int().min(1).max(512),
7093
+ rows: z24.number().int().min(1).max(256)
6577
7094
  })).mutation(async ({ input }) => {
6578
7095
  await agentManager.agentMutation(
6579
7096
  input.agentId,
@@ -6583,9 +7100,9 @@ var terminalRouter = router({
6583
7100
  return { ok: true };
6584
7101
  }),
6585
7102
  /** Destroy a PTY session */
6586
- destroy: protectedProcedure.input(z21.object({
6587
- agentId: z21.string().max(128),
6588
- sessionId: z21.string().max(128)
7103
+ destroy: protectedProcedure.input(z24.object({
7104
+ agentId: z24.string().max(128),
7105
+ sessionId: z24.string().max(128)
6589
7106
  })).mutation(async ({ input }) => {
6590
7107
  await agentManager.agentMutation(
6591
7108
  input.agentId,
@@ -6595,14 +7112,14 @@ var terminalRouter = router({
6595
7112
  return { ok: true };
6596
7113
  }),
6597
7114
  /** Stream PTY output — proxied via AgentWSBridge dynamic subscription */
6598
- onData: protectedProcedure.input(z21.object({
6599
- agentId: z21.string().max(128),
6600
- sessionId: z21.string().max(128),
7115
+ onData: protectedProcedure.input(z24.object({
7116
+ agentId: z24.string().max(128),
7117
+ sessionId: z24.string().max(128),
6601
7118
  /** Rejouer l'historique de la session avant le flux temps réel. */
6602
- replay: z21.boolean().optional()
7119
+ replay: z24.boolean().optional()
6603
7120
  })).subscription(async function* ({ input }) {
6604
7121
  const queue = [];
6605
- let resolve4 = null;
7122
+ let resolve5 = null;
6606
7123
  let done = false;
6607
7124
  let unsubscribe;
6608
7125
  try {
@@ -6612,21 +7129,21 @@ var terminalRouter = router({
6612
7129
  { sessionId: input.sessionId, replay: input.replay },
6613
7130
  (data) => {
6614
7131
  if (typeof data === "string") queue.push(data);
6615
- if (resolve4) {
6616
- resolve4();
6617
- resolve4 = null;
7132
+ if (resolve5) {
7133
+ resolve5();
7134
+ resolve5 = null;
6618
7135
  }
6619
7136
  },
6620
7137
  () => {
6621
7138
  done = true;
6622
- if (resolve4) {
6623
- resolve4();
6624
- resolve4 = null;
7139
+ if (resolve5) {
7140
+ resolve5();
7141
+ resolve5 = null;
6625
7142
  }
6626
7143
  }
6627
7144
  );
6628
7145
  } catch (err) {
6629
- throw new TRPCError18({
7146
+ throw new TRPCError20({
6630
7147
  code: "BAD_REQUEST",
6631
7148
  message: `Cannot subscribe to terminal: ${err.message}`
6632
7149
  });
@@ -6637,7 +7154,7 @@ var terminalRouter = router({
6637
7154
  yield queue.shift();
6638
7155
  } else {
6639
7156
  await new Promise((r) => {
6640
- resolve4 = r;
7157
+ resolve5 = r;
6641
7158
  });
6642
7159
  }
6643
7160
  }
@@ -6648,27 +7165,27 @@ var terminalRouter = router({
6648
7165
  });
6649
7166
 
6650
7167
  // ../server/src/trpc/routers/utility.ts
6651
- import { z as z22 } from "zod";
7168
+ import { z as z25 } from "zod";
6652
7169
  var MAX_WAIT_MS = 5 * 60 * 1e3;
6653
7170
  var utilityRouter = router({
6654
- wait: protectedProcedure.input(z22.object({
6655
- ms: z22.number().int().min(0).max(MAX_WAIT_MS)
7171
+ wait: protectedProcedure.input(z25.object({
7172
+ ms: z25.number().int().min(0).max(MAX_WAIT_MS)
6656
7173
  })).mutation(async ({ input }) => {
6657
- await new Promise((resolve4) => setTimeout(resolve4, input.ms));
7174
+ await new Promise((resolve5) => setTimeout(resolve5, input.ms));
6658
7175
  return { waited: input.ms };
6659
7176
  })
6660
7177
  });
6661
7178
 
6662
7179
  // ../server/src/trpc/routers/wiki.ts
6663
- import { z as z23 } from "zod";
6664
- import { TRPCError as TRPCError19 } from "@trpc/server";
6665
- import { readFile as readFile3, readdir as readdir2, stat as stat4, realpath, writeFile as writeFile5, mkdir as mkdir6, rename, rm as rm2 } from "fs/promises";
6666
- import { resolve as resolvePath, join as join8, relative as relative2, sep, dirname as dirname4 } from "path";
6667
- var SCOPE_SCHEMA = z23.union([
6668
- z23.literal("global"),
6669
- z23.string().regex(/^service:[A-Za-z0-9_-]{1,128}$/, "Invalid service scope")
7180
+ import { z as z26 } from "zod";
7181
+ import { TRPCError as TRPCError21 } from "@trpc/server";
7182
+ import { readFile as readFile5, readdir as readdir2, stat as stat4, realpath as realpath2, writeFile as writeFile5, mkdir as mkdir7, rename, rm as rm2 } from "fs/promises";
7183
+ import { resolve as resolvePath, join as join9, relative as relative2, sep, dirname as dirname5 } from "path";
7184
+ var SCOPE_SCHEMA = z26.union([
7185
+ z26.literal("global"),
7186
+ z26.string().regex(/^service:[A-Za-z0-9_-]{1,128}$/, "Invalid service scope")
6670
7187
  ]);
6671
- var REL_PATH_SCHEMA = z23.string().min(1).max(512).regex(/^[A-Za-z0-9_./-]+$/).refine((p) => !p.includes("..") && !p.startsWith("/"), {
7188
+ var REL_PATH_SCHEMA = z26.string().min(1).max(512).regex(/^[A-Za-z0-9_./-]+$/).refine((p) => !p.includes("..") && !p.startsWith("/"), {
6672
7189
  message: "Path must be a relative path without traversal"
6673
7190
  });
6674
7191
  var MAX_FILES = 500;
@@ -6688,36 +7205,36 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
6688
7205
  "target",
6689
7206
  "out"
6690
7207
  ]);
6691
- var DocsEntrySchema = z23.lazy(() => z23.union([
6692
- z23.object({ type: z23.literal("file"), name: z23.string(), path: z23.string() }),
6693
- z23.object({ type: z23.literal("dir"), name: z23.string(), path: z23.string(), children: z23.array(DocsEntrySchema) })
7208
+ var DocsEntrySchema = z26.lazy(() => z26.union([
7209
+ z26.object({ type: z26.literal("file"), name: z26.string(), path: z26.string() }),
7210
+ z26.object({ type: z26.literal("dir"), name: z26.string(), path: z26.string(), children: z26.array(DocsEntrySchema) })
6694
7211
  ]));
6695
7212
  async function resolveScope(scope) {
6696
7213
  if (scope === "global") {
6697
7214
  const settings = await settingsManager.getSettings();
6698
7215
  const sub = (settings.globalDocsPath?.trim() || "wiki").replace(/^[\\/]+|[\\/]+$/g, "");
6699
7216
  if (sub.includes("..") || sub.includes("\0")) {
6700
- throw new TRPCError19({ code: "BAD_REQUEST", message: "Sous-chemin invalide" });
7217
+ throw new TRPCError21({ code: "BAD_REQUEST", message: "Sous-chemin invalide" });
6701
7218
  }
6702
7219
  const dataDirAbs = resolvePath(env.DATA_DIR);
6703
- await mkdir6(dataDirAbs, { recursive: true });
6704
- const base = dirname4(await realpath(dataDirAbs));
7220
+ await mkdir7(dataDirAbs, { recursive: true });
7221
+ const base = dirname5(await realpath2(dataDirAbs));
6705
7222
  const target = resolvePath(base, sub);
6706
7223
  if (target !== base && !(target + sep).startsWith(base + sep)) {
6707
- throw new TRPCError19({ code: "FORBIDDEN", message: "Hors du dossier de donn\xE9es du serveur" });
7224
+ throw new TRPCError21({ code: "FORBIDDEN", message: "Hors du dossier de donn\xE9es du serveur" });
6708
7225
  }
6709
- await mkdir6(target, { recursive: true });
6710
- const root = await realpath(target);
7226
+ await mkdir7(target, { recursive: true });
7227
+ const root = await realpath2(target);
6711
7228
  if (root !== base && !(root + sep).startsWith(base + sep)) {
6712
- throw new TRPCError19({ code: "FORBIDDEN", message: "Le dossier wiki r\xE9sout hors du dossier de donn\xE9es" });
7229
+ throw new TRPCError21({ code: "FORBIDDEN", message: "Le dossier wiki r\xE9sout hors du dossier de donn\xE9es" });
6713
7230
  }
6714
7231
  return { mode: "local", root };
6715
7232
  }
6716
7233
  const serviceId = scope.slice("service:".length);
6717
7234
  const service = await serviceStore.get(serviceId);
6718
- if (!service) throw new TRPCError19({ code: "NOT_FOUND", message: "Service introuvable" });
7235
+ if (!service) throw new TRPCError21({ code: "NOT_FOUND", message: "Service introuvable" });
6719
7236
  const agentId = await resolveEffectiveAgentId(service);
6720
- if (!agentId) throw new TRPCError19({ code: "BAD_REQUEST", message: "Aucun agent configur\xE9 pour ce service" });
7237
+ if (!agentId) throw new TRPCError21({ code: "BAD_REQUEST", message: "Aucun agent configur\xE9 pour ce service" });
6721
7238
  const cwd = await resolveWorkspaceCwd({ serviceId, override: service.docsPath?.trim() || void 0 });
6722
7239
  return { mode: "agent", agentId, cwd };
6723
7240
  }
@@ -6733,7 +7250,7 @@ async function walkLocal(root, dir, depth, counter) {
6733
7250
  for (const e of entries2) {
6734
7251
  if (counter.n >= MAX_FILES) break;
6735
7252
  if (e.name.startsWith(".") && e.name !== ".") continue;
6736
- const abs = join8(dir, e.name);
7253
+ const abs = join9(dir, e.name);
6737
7254
  const rel = relative2(root, abs).split(sep).join("/");
6738
7255
  if (e.isDirectory()) {
6739
7256
  if (IGNORED_DIRS.has(e.name)) continue;
@@ -6752,35 +7269,35 @@ async function walkLocal(root, dir, depth, counter) {
6752
7269
  }
6753
7270
  async function resolveSafeLocal(root, relPath, requireExist) {
6754
7271
  if (relPath.includes("..") || relPath.startsWith("/")) {
6755
- throw new TRPCError19({ code: "BAD_REQUEST", message: "Chemin invalide" });
7272
+ throw new TRPCError21({ code: "BAD_REQUEST", message: "Chemin invalide" });
6756
7273
  }
6757
7274
  const target = resolvePath(root, relPath);
6758
7275
  if (requireExist) {
6759
7276
  let real;
6760
7277
  try {
6761
- real = await realpath(target);
7278
+ real = await realpath2(target);
6762
7279
  } catch {
6763
- throw new TRPCError19({ code: "NOT_FOUND", message: "Chemin introuvable" });
7280
+ throw new TRPCError21({ code: "NOT_FOUND", message: "Chemin introuvable" });
6764
7281
  }
6765
7282
  if (real !== root && !(real + sep).startsWith(root + sep)) {
6766
- throw new TRPCError19({ code: "FORBIDDEN", message: "Hors du dossier autoris\xE9" });
7283
+ throw new TRPCError21({ code: "FORBIDDEN", message: "Hors du dossier autoris\xE9" });
6767
7284
  }
6768
7285
  return real;
6769
7286
  }
6770
- const parent = dirname4(target);
7287
+ const parent = dirname5(target);
6771
7288
  let realParent;
6772
7289
  try {
6773
- realParent = await realpath(parent);
7290
+ realParent = await realpath2(parent);
6774
7291
  } catch {
6775
7292
  realParent = parent;
6776
7293
  }
6777
7294
  if (realParent !== root && !(realParent + sep).startsWith(root + sep)) {
6778
- throw new TRPCError19({ code: "FORBIDDEN", message: "Hors du dossier autoris\xE9" });
7295
+ throw new TRPCError21({ code: "FORBIDDEN", message: "Hors du dossier autoris\xE9" });
6779
7296
  }
6780
7297
  return resolvePath(realParent, target.slice(parent.length).replace(/^[\\/]+/, ""));
6781
7298
  }
6782
7299
  var wikiRouter = router({
6783
- list: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA })).output(z23.object({ root: z23.string(), entries: z23.array(DocsEntrySchema), truncated: z23.boolean() })).query(async ({ input }) => {
7300
+ list: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA })).output(z26.object({ root: z26.string(), entries: z26.array(DocsEntrySchema), truncated: z26.boolean() })).query(async ({ input }) => {
6784
7301
  const target = await resolveScope(input.scope);
6785
7302
  if (target.mode === "agent") {
6786
7303
  return agentManager.agentQuery(target.agentId, "wiki.listRepoMarkdown", { cwd: target.cwd });
@@ -6789,9 +7306,9 @@ var wikiRouter = router({
6789
7306
  const entries2 = await walkLocal(target.root, target.root, 0, counter);
6790
7307
  return { root: target.root, entries: entries2, truncated: counter.n >= MAX_FILES };
6791
7308
  }),
6792
- read: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).output(z23.object({ path: z23.string(), content: z23.string() })).query(async ({ input }) => {
7309
+ read: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).output(z26.object({ path: z26.string(), content: z26.string() })).query(async ({ input }) => {
6793
7310
  if (!/\.(md|markdown)$/i.test(input.path)) {
6794
- throw new TRPCError19({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
7311
+ throw new TRPCError21({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
6795
7312
  }
6796
7313
  const target = await resolveScope(input.scope);
6797
7314
  if (target.mode === "agent") {
@@ -6799,27 +7316,27 @@ var wikiRouter = router({
6799
7316
  }
6800
7317
  const file = await resolveSafeLocal(target.root, input.path, true);
6801
7318
  const s = await stat4(file);
6802
- if (!s.isFile()) throw new TRPCError19({ code: "BAD_REQUEST", message: "Pas un fichier" });
6803
- if (s.size > MAX_FILE_BYTES) throw new TRPCError19({ code: "PAYLOAD_TOO_LARGE", message: "Fichier trop volumineux" });
6804
- const content = await readFile3(file, "utf-8");
7319
+ if (!s.isFile()) throw new TRPCError21({ code: "BAD_REQUEST", message: "Pas un fichier" });
7320
+ if (s.size > MAX_FILE_BYTES) throw new TRPCError21({ code: "PAYLOAD_TOO_LARGE", message: "Fichier trop volumineux" });
7321
+ const content = await readFile5(file, "utf-8");
6805
7322
  return { path: input.path, content };
6806
7323
  }),
6807
- write: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA, content: z23.string().max(5e6) })).mutation(async ({ input }) => {
7324
+ write: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA, content: z26.string().max(5e6) })).mutation(async ({ input }) => {
6808
7325
  if (!/\.(md|markdown)$/i.test(input.path)) {
6809
- throw new TRPCError19({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
7326
+ throw new TRPCError21({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
6810
7327
  }
6811
7328
  const target = await resolveScope(input.scope);
6812
7329
  if (target.mode === "agent") {
6813
7330
  return agentManager.agentMutation(target.agentId, "wiki.writeMarkdown", { cwd: target.cwd, path: input.path, content: input.content });
6814
7331
  }
6815
7332
  const file = await resolveSafeLocal(target.root, input.path, false);
6816
- await mkdir6(dirname4(file), { recursive: true });
7333
+ await mkdir7(dirname5(file), { recursive: true });
6817
7334
  await writeFile5(file, input.content, "utf-8");
6818
7335
  return { path: input.path, ok: true };
6819
7336
  }),
6820
- createFile: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
7337
+ createFile: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
6821
7338
  if (!/\.(md|markdown)$/i.test(input.path)) {
6822
- throw new TRPCError19({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
7339
+ throw new TRPCError21({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
6823
7340
  }
6824
7341
  const target = await resolveScope(input.scope);
6825
7342
  if (target.mode === "agent") {
@@ -6828,24 +7345,24 @@ var wikiRouter = router({
6828
7345
  const file = await resolveSafeLocal(target.root, input.path, false);
6829
7346
  try {
6830
7347
  await stat4(file);
6831
- throw new TRPCError19({ code: "CONFLICT", message: "Fichier d\xE9j\xE0 existant" });
7348
+ throw new TRPCError21({ code: "CONFLICT", message: "Fichier d\xE9j\xE0 existant" });
6832
7349
  } catch (err) {
6833
- if (err instanceof TRPCError19) throw err;
7350
+ if (err instanceof TRPCError21) throw err;
6834
7351
  }
6835
- await mkdir6(dirname4(file), { recursive: true });
7352
+ await mkdir7(dirname5(file), { recursive: true });
6836
7353
  await writeFile5(file, "", "utf-8");
6837
7354
  return { path: input.path, ok: true };
6838
7355
  }),
6839
- createDir: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
7356
+ createDir: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
6840
7357
  const target = await resolveScope(input.scope);
6841
7358
  if (target.mode === "agent") {
6842
7359
  return agentManager.agentMutation(target.agentId, "wiki.createDir", { cwd: target.cwd, path: input.path });
6843
7360
  }
6844
7361
  const dir = await resolveSafeLocal(target.root, input.path, false);
6845
- await mkdir6(dir, { recursive: true });
7362
+ await mkdir7(dir, { recursive: true });
6846
7363
  return { path: input.path, ok: true };
6847
7364
  }),
6848
- rename: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, fromPath: REL_PATH_SCHEMA, toPath: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
7365
+ rename: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, fromPath: REL_PATH_SCHEMA, toPath: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
6849
7366
  const target = await resolveScope(input.scope);
6850
7367
  if (target.mode === "agent") {
6851
7368
  return agentManager.agentMutation(target.agentId, "wiki.renamePath", { cwd: target.cwd, fromPath: input.fromPath, toPath: input.toPath });
@@ -6854,34 +7371,34 @@ var wikiRouter = router({
6854
7371
  const to = await resolveSafeLocal(target.root, input.toPath, false);
6855
7372
  try {
6856
7373
  await stat4(to);
6857
- throw new TRPCError19({ code: "CONFLICT", message: "La cible existe d\xE9j\xE0" });
7374
+ throw new TRPCError21({ code: "CONFLICT", message: "La cible existe d\xE9j\xE0" });
6858
7375
  } catch (err) {
6859
- if (err instanceof TRPCError19) throw err;
7376
+ if (err instanceof TRPCError21) throw err;
6860
7377
  }
6861
- await mkdir6(dirname4(to), { recursive: true });
7378
+ await mkdir7(dirname5(to), { recursive: true });
6862
7379
  await rename(from, to);
6863
7380
  return { fromPath: input.fromPath, toPath: input.toPath, ok: true };
6864
7381
  }),
6865
- ensureRoot: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA })).mutation(async ({ input }) => {
7382
+ ensureRoot: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA })).mutation(async ({ input }) => {
6866
7383
  if (input.scope === "global") {
6867
7384
  await resolveScope("global");
6868
7385
  return { ok: true };
6869
7386
  }
6870
7387
  const serviceId = input.scope.slice("service:".length);
6871
7388
  const service = await serviceStore.get(serviceId);
6872
- if (!service) throw new TRPCError19({ code: "NOT_FOUND", message: "Service introuvable" });
7389
+ if (!service) throw new TRPCError21({ code: "NOT_FOUND", message: "Service introuvable" });
6873
7390
  const agentId = await resolveEffectiveAgentId(service);
6874
- if (!agentId) throw new TRPCError19({ code: "BAD_REQUEST", message: "Aucun agent configur\xE9" });
7391
+ if (!agentId) throw new TRPCError21({ code: "BAD_REQUEST", message: "Aucun agent configur\xE9" });
6875
7392
  const cwd = await resolveWorkspaceCwd({ serviceId, override: service.docsPath?.trim() || void 0 });
6876
7393
  return agentManager.agentMutation(agentId, "wiki.ensureRoot", { cwd });
6877
7394
  }),
6878
- delete: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
7395
+ delete: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
6879
7396
  const target = await resolveScope(input.scope);
6880
7397
  if (target.mode === "agent") {
6881
7398
  return agentManager.agentMutation(target.agentId, "wiki.deletePath", { cwd: target.cwd, path: input.path });
6882
7399
  }
6883
7400
  const fp = await resolveSafeLocal(target.root, input.path, true);
6884
- if (fp === target.root) throw new TRPCError19({ code: "FORBIDDEN", message: "Impossible de supprimer la racine" });
7401
+ if (fp === target.root) throw new TRPCError21({ code: "FORBIDDEN", message: "Impossible de supprimer la racine" });
6885
7402
  await rm2(fp, { recursive: true, force: true });
6886
7403
  return { path: input.path, ok: true };
6887
7404
  })
@@ -6903,14 +7420,14 @@ var configRouter = router({
6903
7420
  });
6904
7421
 
6905
7422
  // ../server/src/trpc/routers/cloud-sync.ts
6906
- import { z as z24 } from "zod";
6907
- import { TRPCError as TRPCError20 } from "@trpc/server";
7423
+ import { z as z27 } from "zod";
7424
+ import { TRPCError as TRPCError22 } from "@trpc/server";
6908
7425
 
6909
7426
  // ../server/src/services/org-sync-scope.ts
6910
7427
  import { access } from "fs/promises";
6911
- import { join as join9 } from "path";
7428
+ import { join as join10 } from "path";
6912
7429
  async function isMaterialized(root) {
6913
- return access(join9(root, "project.json")).then(() => true, () => false);
7430
+ return access(join10(root, "project.json")).then(() => true, () => false);
6914
7431
  }
6915
7432
  async function orgsToSync() {
6916
7433
  const [stamps, stampedAccounts, accounts] = await Promise.all([
@@ -6985,23 +7502,23 @@ async function reconcileOrgCanary(orgId) {
6985
7502
  }
6986
7503
 
6987
7504
  // ../server/src/services/cloud-sync-bridge.ts
6988
- import { randomUUID as randomUUID7 } from "crypto";
6989
- import { mkdir as mkdir7, rm as rm3 } from "fs/promises";
7505
+ import { randomUUID as randomUUID8 } from "crypto";
7506
+ import { mkdir as mkdir8, rm as rm3 } from "fs/promises";
6990
7507
  import * as Y2 from "yjs";
6991
7508
 
6992
7509
  // ../server/src/services/sync-doc-cache.ts
6993
7510
  import { readFileSync as readFileSync2, renameSync, rmSync, writeFileSync, mkdirSync } from "fs";
6994
- import { readFile as readFile4 } from "fs/promises";
6995
- import { dirname as dirname5, join as join10 } from "path";
6996
- import { randomUUID as randomUUID6 } from "crypto";
7511
+ import { readFile as readFile6 } from "fs/promises";
7512
+ import { dirname as dirname6, join as join11 } from "path";
7513
+ import { randomUUID as randomUUID7 } from "crypto";
6997
7514
  import * as Y from "yjs";
6998
- var FILE = "sync-doc.bin";
7515
+ var FILE2 = "sync-doc.bin";
6999
7516
  function cachePath(orgId) {
7000
- return join10(orgVaultRoot(orgId), FILE);
7517
+ return join11(orgVaultRoot(orgId), FILE2);
7001
7518
  }
7002
7519
  async function readSyncDoc(orgId) {
7003
7520
  try {
7004
- return new Uint8Array(await readFile4(cachePath(orgId)));
7521
+ return new Uint8Array(await readFile6(cachePath(orgId)));
7005
7522
  } catch {
7006
7523
  return null;
7007
7524
  }
@@ -7024,8 +7541,8 @@ function writeSyncDocSync(orgId, state) {
7024
7541
  existing = new Uint8Array(readFileSync2(path));
7025
7542
  } catch {
7026
7543
  }
7027
- mkdirSync(dirname5(path), { recursive: true });
7028
- const tmp = `${path}.${process.pid}.${randomUUID6()}.tmp`;
7544
+ mkdirSync(dirname6(path), { recursive: true });
7545
+ const tmp = `${path}.${process.pid}.${randomUUID7()}.tmp`;
7029
7546
  try {
7030
7547
  writeFileSync(tmp, mergeWith(existing, state), { mode: 384 });
7031
7548
  renameSync(tmp, path);
@@ -7078,7 +7595,7 @@ var OrgSyncConnection = class {
7078
7595
  services = getCollection(this.doc, "services");
7079
7596
  environments = getCollection(this.doc, "environments");
7080
7597
  logParsers = getCollection(this.doc, PARSERS);
7081
- conn = randomUUID7();
7598
+ conn = randomUUID8();
7082
7599
  link;
7083
7600
  stopped = false;
7084
7601
  initializing = true;
@@ -7128,8 +7645,8 @@ var OrgSyncConnection = class {
7128
7645
  * Réglée quand le document tient l'état d'ouverture du cloud — ou quand il ne
7129
7646
  * l'aura pas : session refusée, app trop ancienne, connexion arrêtée.
7130
7647
  */
7131
- opened = new Promise((resolve4) => {
7132
- this.markOpened = resolve4;
7648
+ opened = new Promise((resolve5) => {
7649
+ this.markOpened = resolve5;
7133
7650
  });
7134
7651
  /** Store applications started by remote events and not finished yet. */
7135
7652
  applying = /* @__PURE__ */ new Set();
@@ -7511,7 +8028,7 @@ var OrgSyncConnection = class {
7511
8028
  }
7512
8029
  const isLocal = existing?.orgId === this.link.orgId;
7513
8030
  if (!inDoc && !isLocal) return false;
7514
- await mkdir7(projectVaultRoot(this.link.orgId, projectId), { recursive: true });
8031
+ await mkdir8(projectVaultRoot(this.link.orgId, projectId), { recursive: true });
7515
8032
  this.tracked.add(projectId);
7516
8033
  if (inDoc) await this.pullFromDoc([projectId]);
7517
8034
  else await this.reconcileLocalToDoc();
@@ -7990,8 +8507,8 @@ var CloudSyncBridge = class {
7990
8507
  let timer;
7991
8508
  await Promise.race([
7992
8509
  conn.opened,
7993
- new Promise((resolve4) => {
7994
- timer = setTimeout(resolve4, timeoutMs);
8510
+ new Promise((resolve5) => {
8511
+ timer = setTimeout(resolve5, timeoutMs);
7995
8512
  })
7996
8513
  ]);
7997
8514
  clearTimeout(timer);
@@ -8008,9 +8525,9 @@ var CloudSyncBridge = class {
8008
8525
  var cloudSyncBridge = new CloudSyncBridge();
8009
8526
 
8010
8527
  // ../server/src/services/key-adoption.ts
8011
- import { readdir as readdir3, readFile as readFile5, writeFile as writeFile6, mkdir as mkdir8, copyFile, rename as rename2, stat as stat5 } from "fs/promises";
8012
- import { dirname as dirname6, join as join11 } from "path";
8013
- import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes2 } from "crypto";
8528
+ import { readdir as readdir3, readFile as readFile7, writeFile as writeFile6, mkdir as mkdir9, copyFile, rename as rename2, stat as stat5 } from "fs/promises";
8529
+ import { dirname as dirname7, join as join12 } from "path";
8530
+ import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes3 } from "crypto";
8014
8531
  var ALGORITHM = "aes-256-gcm";
8015
8532
  function isAgentTriplet(value) {
8016
8533
  const hex = (v) => typeof v === "string" && /^[0-9a-f]+$/i.test(v);
@@ -8022,7 +8539,7 @@ function decryptTriplet(key, t) {
8022
8539
  return decipher.update(t.encrypted, "hex", "utf-8") + decipher.final("utf-8");
8023
8540
  }
8024
8541
  function encryptTriplet(key, plaintext) {
8025
- const iv = randomBytes2(16);
8542
+ const iv = randomBytes3(16);
8026
8543
  const cipher = createCipheriv2(ALGORITHM, key, iv);
8027
8544
  const encrypted = cipher.update(plaintext, "utf-8", "hex") + cipher.final("hex");
8028
8545
  return { encrypted, iv: iv.toString("hex"), authTag: cipher.getAuthTag().toString("hex") };
@@ -8076,7 +8593,7 @@ async function jsonNamesIn(dir) {
8076
8593
  async function jsonFilesIn(root) {
8077
8594
  const names = await jsonNamesIn(root);
8078
8595
  for (const sub of ENCRYPTED_SUBDIRS) {
8079
- names.push(...(await jsonNamesIn(join11(root, sub))).map((n) => join11(sub, n)));
8596
+ names.push(...(await jsonNamesIn(join12(root, sub))).map((n) => join12(sub, n)));
8080
8597
  }
8081
8598
  return names;
8082
8599
  }
@@ -8114,10 +8631,10 @@ async function adoptOrgKey(orgId, newKey, masterPassword, now) {
8114
8631
  const touchedRoots = /* @__PURE__ */ new Set();
8115
8632
  for (const vaultRoot of await rootsOfOrg(orgId)) {
8116
8633
  for (const name of await jsonFilesIn(vaultRoot)) {
8117
- const path = join11(vaultRoot, name);
8634
+ const path = join12(vaultRoot, name);
8118
8635
  let parsed;
8119
8636
  try {
8120
- parsed = JSON.parse(await readFile5(path, "utf-8"));
8637
+ parsed = JSON.parse(await readFile7(path, "utf-8"));
8121
8638
  } catch {
8122
8639
  continue;
8123
8640
  }
@@ -8131,15 +8648,15 @@ async function adoptOrgKey(orgId, newKey, masterPassword, now) {
8131
8648
  const stamp = now.toISOString().replace(/[:.]/g, "-").substring(0, 19);
8132
8649
  const backups = [];
8133
8650
  for (const vaultRoot of touchedRoots) {
8134
- const backupDir = join11(vaultRoot, `backup-key-${stamp}`);
8135
- await mkdir8(backupDir, { recursive: true });
8651
+ const backupDir = join12(vaultRoot, `backup-key-${stamp}`);
8652
+ await mkdir9(backupDir, { recursive: true });
8136
8653
  for (const name of await jsonFilesIn(vaultRoot)) {
8137
- await mkdir8(dirname6(join11(backupDir, name)), { recursive: true });
8138
- await copyFile(join11(vaultRoot, name), join11(backupDir, name));
8654
+ await mkdir9(dirname7(join12(backupDir, name)), { recursive: true });
8655
+ await copyFile(join12(vaultRoot, name), join12(backupDir, name));
8139
8656
  }
8140
8657
  for (const name of [".encryption.key.enc", ".runeya-verify"]) {
8141
- if (await exists(join11(vaultRoot, name))) {
8142
- await copyFile(join11(vaultRoot, name), join11(backupDir, name));
8658
+ if (await exists(join12(vaultRoot, name))) {
8659
+ await copyFile(join12(vaultRoot, name), join12(backupDir, name));
8143
8660
  }
8144
8661
  }
8145
8662
  backups.push(backupDir);
@@ -8158,7 +8675,7 @@ async function adoptOrgKey(orgId, newKey, masterPassword, now) {
8158
8675
  return { converted: pending.map(({ path, values }) => ({ file: path, values })), backups };
8159
8676
  }
8160
8677
  async function resetOrgKeyToFresh(orgId, masterPassword, now) {
8161
- const freshKey = randomBytes2(32);
8678
+ const freshKey = randomBytes3(32);
8162
8679
  if (isVaultLocked(orgId) || !getEncryptionKey(orgId)) {
8163
8680
  await readEncryptedKey(await keyRoot(), masterPassword);
8164
8681
  await installOrgKey(orgId, freshKey, masterPassword);
@@ -8194,7 +8711,7 @@ async function assertOrgReadable(orgId) {
8194
8711
  await assertOrgVaultReadable(orgId);
8195
8712
  } catch (err) {
8196
8713
  if (err instanceof OrgVaultUnreadableError) {
8197
- throw new TRPCError20({ code: "FORBIDDEN", message: err.message });
8714
+ throw new TRPCError22({ code: "FORBIDDEN", message: err.message });
8198
8715
  }
8199
8716
  throw err;
8200
8717
  }
@@ -8204,10 +8721,10 @@ async function assertOrgKey(orgId) {
8204
8721
  await reconcileOrgCanary(orgId);
8205
8722
  } catch (err) {
8206
8723
  if (err instanceof OrgKeyMismatchError) {
8207
- throw new TRPCError20({ code: "FORBIDDEN", message: err.message });
8724
+ throw new TRPCError22({ code: "FORBIDDEN", message: err.message });
8208
8725
  }
8209
8726
  if (err instanceof CanaryUnavailableError) {
8210
- throw new TRPCError20({ code: "PRECONDITION_FAILED", message: err.message });
8727
+ throw new TRPCError22({ code: "PRECONDITION_FAILED", message: err.message });
8211
8728
  }
8212
8729
  throw err;
8213
8730
  }
@@ -8224,14 +8741,14 @@ var cloudSyncRouter = router({
8224
8741
  * "open this project" is the whole gesture.
8225
8742
  */
8226
8743
  trackProject: protectedProcedure.input(
8227
- z24.object({
8228
- orgId: z24.string().min(1).max(128),
8229
- cloudUrl: z24.string().url().max(2048),
8230
- projectId: z24.string().min(1).max(128),
8744
+ z27.object({
8745
+ orgId: z27.string().min(1).max(128),
8746
+ cloudUrl: z27.string().url().max(2048),
8747
+ projectId: z27.string().min(1).max(128),
8231
8748
  // Le compte sous lequel cette organisation a été listée. Tamponné avec
8232
8749
  // elle : c'est ce qui permettra à tout appel ultérieur de retrouver le
8233
8750
  // bon token sans qu'on ait à le lui redire.
8234
- accountId: z24.string().max(64).optional()
8751
+ accountId: z27.string().max(64).optional()
8235
8752
  })
8236
8753
  ).mutation(async ({ input }) => {
8237
8754
  await assertOrgReadable(input.orgId);
@@ -8253,7 +8770,7 @@ var cloudSyncRouter = router({
8253
8770
  return { pulled, state: syncState(input.orgId) };
8254
8771
  } catch (err) {
8255
8772
  if (err instanceof ProjectHeldElsewhereError) {
8256
- throw new TRPCError20({ code: "CONFLICT", message: err.code, cause: err });
8773
+ throw new TRPCError22({ code: "CONFLICT", message: err.code, cause: err });
8257
8774
  }
8258
8775
  throw err;
8259
8776
  }
@@ -8272,22 +8789,22 @@ var cloudSyncRouter = router({
8272
8789
  * fusionné.
8273
8790
  */
8274
8791
  publishProject: protectedProcedure.input(
8275
- z24.object({
8276
- orgId: z24.string().min(1).max(128),
8277
- cloudUrl: z24.string().url().max(2048),
8278
- projectId: z24.string().min(1).max(128),
8792
+ z27.object({
8793
+ orgId: z27.string().min(1).max(128),
8794
+ cloudUrl: z27.string().url().max(2048),
8795
+ projectId: z27.string().min(1).max(128),
8279
8796
  // Le compte sous lequel cette organisation a été listée. Tamponné avec
8280
8797
  // elle : c'est ce qui permettra à tout appel ultérieur de retrouver le
8281
8798
  // bon token sans qu'on ait à le lui redire.
8282
- accountId: z24.string().max(64).optional()
8799
+ accountId: z27.string().max(64).optional()
8283
8800
  })
8284
8801
  ).mutation(async ({ input }) => {
8285
8802
  const local = await projectStore.get({ id: input.projectId, orgId: null });
8286
8803
  if (!local) {
8287
- throw new TRPCError20({ code: "NOT_FOUND", message: "PROJECT_NOT_LOCAL" });
8804
+ throw new TRPCError22({ code: "NOT_FOUND", message: "PROJECT_NOT_LOCAL" });
8288
8805
  }
8289
8806
  if (await projectStore.get({ id: input.projectId, orgId: input.orgId })) {
8290
- throw new TRPCError20({ code: "CONFLICT", message: "PROJECT_ALREADY_IN_ORG" });
8807
+ throw new TRPCError22({ code: "CONFLICT", message: "PROJECT_ALREADY_IN_ORG" });
8291
8808
  }
8292
8809
  await assertOrgReadable(input.orgId);
8293
8810
  const connected = cloudSyncBridge.states().some((s) => s.orgId === input.orgId);
@@ -8312,7 +8829,7 @@ var cloudSyncRouter = router({
8312
8829
  await cloudSyncBridge.followProject(input.orgId, input.projectId);
8313
8830
  } catch (err) {
8314
8831
  if (err instanceof ProjectHeldElsewhereError) {
8315
- throw new TRPCError20({ code: "CONFLICT", message: err.code, cause: err });
8832
+ throw new TRPCError22({ code: "CONFLICT", message: err.code, cause: err });
8316
8833
  }
8317
8834
  throw err;
8318
8835
  }
@@ -8326,16 +8843,16 @@ var cloudSyncRouter = router({
8326
8843
  * publié. Le cloud, lui, ne peut pas répondre — il n'a que du chiffré sous
8327
8844
  * les yeux — donc le canari est rapatrié et jugé ici.
8328
8845
  */
8329
- checkOrgKey: protectedProcedure.input(z24.object({
8330
- orgId: z24.string().min(1).max(128),
8331
- keyHex: z24.string().length(64).regex(/^[0-9a-f]+$/i)
8846
+ checkOrgKey: protectedProcedure.input(z27.object({
8847
+ orgId: z27.string().min(1).max(128),
8848
+ keyHex: z27.string().length(64).regex(/^[0-9a-f]+$/i)
8332
8849
  })).query(async ({ input }) => {
8333
8850
  const res = await cloudFetch2(
8334
8851
  `/api/auth/organization/canary?organizationId=${encodeURIComponent(input.orgId)}`,
8335
8852
  { method: "GET", accountId: await accountForOrgScope(input.orgId) }
8336
8853
  );
8337
8854
  if (!res.ok) {
8338
- throw new TRPCError20({ code: "BAD_GATEWAY", message: await cloudMessage(res) || `CANARY_READ_FAILED (${res.status})` });
8855
+ throw new TRPCError22({ code: "BAD_GATEWAY", message: await cloudMessage(res) || `CANARY_READ_FAILED (${res.status})` });
8339
8856
  }
8340
8857
  const { canary } = await res.json();
8341
8858
  if (typeof canary !== "string") return { matches: false, published: false };
@@ -8350,7 +8867,7 @@ var cloudSyncRouter = router({
8350
8867
  * que l'interface puisse proposer ni l'adoption ni la remise à zéro — le seul
8351
8868
  * chemin qui la répare. Rien n'est écrit ici, ni sur le disque ni sur le cloud.
8352
8869
  */
8353
- orgKeyStatus: protectedProcedure.input(z24.object({ orgId: z24.string().min(1).max(128) })).query(async ({ input }) => {
8870
+ orgKeyStatus: protectedProcedure.input(z27.object({ orgId: z27.string().min(1).max(128) })).query(async ({ input }) => {
8354
8871
  const key = getEncryptionKey(input.orgId);
8355
8872
  if (!key) return { status: "unknown" };
8356
8873
  const res = await cloudFetch2(
@@ -8373,10 +8890,10 @@ var cloudSyncRouter = router({
8373
8890
  * Bornée au coffre de l'organisation et à ceux de ses projets : les autres
8374
8891
  * organisations et la machine gardent leur clé.
8375
8892
  */
8376
- adoptOrgKey: protectedProcedure.input(z24.object({
8377
- orgId: z24.string().min(1).max(128),
8378
- keyHex: z24.string().length(64).regex(/^[0-9a-f]+$/i),
8379
- masterPassword: z24.string().min(1).max(1024)
8893
+ adoptOrgKey: protectedProcedure.input(z27.object({
8894
+ orgId: z27.string().min(1).max(128),
8895
+ keyHex: z27.string().length(64).regex(/^[0-9a-f]+$/i),
8896
+ masterPassword: z27.string().min(1).max(1024)
8380
8897
  })).mutation(async ({ input }) => {
8381
8898
  const report = await adoptOrgKey(
8382
8899
  input.orgId,
@@ -8406,14 +8923,14 @@ var cloudSyncRouter = router({
8406
8923
  * Ce qui a déjà été synchronisé sous l'ancienne clé de référence reste
8407
8924
  * illisible : aucune clé d'ici ne l'ouvre. L'interface le dit avant.
8408
8925
  */
8409
- resetOrgKey: protectedProcedure.input(z24.object({
8410
- orgId: z24.string().min(1).max(128),
8411
- masterPassword: z24.string().min(1).max(1024)
8926
+ resetOrgKey: protectedProcedure.input(z27.object({
8927
+ orgId: z27.string().min(1).max(128),
8928
+ masterPassword: z27.string().min(1).max(1024)
8412
8929
  })).mutation(async ({ input }) => {
8413
8930
  const { keyHex } = await resetOrgKeyToFresh(input.orgId, input.masterPassword, /* @__PURE__ */ new Date());
8414
8931
  const canary = await readVerifyFile(orgVaultRoot(input.orgId));
8415
8932
  if (!canary) {
8416
- throw new TRPCError20({
8933
+ throw new TRPCError22({
8417
8934
  code: "PRECONDITION_FAILED",
8418
8935
  message: "Le coffre de cette organisation n'a pas re\xE7u sa cl\xE9."
8419
8936
  });
@@ -8425,7 +8942,7 @@ var cloudSyncRouter = router({
8425
8942
  });
8426
8943
  if (!res.ok) {
8427
8944
  const detail = await cloudMessage(res);
8428
- throw new TRPCError20({
8945
+ throw new TRPCError22({
8429
8946
  code: res.status === 403 ? "FORBIDDEN" : "BAD_GATEWAY",
8430
8947
  message: detail || `ORG_KEY_RESET_FAILED (${res.status})`
8431
8948
  });
@@ -8441,9 +8958,9 @@ var cloudSyncRouter = router({
8441
8958
  * every other member and for this one on another machine.
8442
8959
  */
8443
8960
  untrackProject: protectedProcedure.input(
8444
- z24.object({
8445
- orgId: z24.string().min(1).max(128),
8446
- projectId: z24.string().min(1).max(128)
8961
+ z27.object({
8962
+ orgId: z27.string().min(1).max(128),
8963
+ projectId: z27.string().min(1).max(128)
8447
8964
  })
8448
8965
  ).mutation(async ({ input }) => {
8449
8966
  await cloudSyncBridge.unfollowProject(input.orgId, input.projectId);
@@ -8459,9 +8976,9 @@ var cloudSyncRouter = router({
8459
8976
  * membre, ce qui évite d'effacer les fichiers si le cloud a refusé l'ordre.
8460
8977
  */
8461
8978
  deleteProjectFromOrg: protectedProcedure.input(
8462
- z24.object({
8463
- orgId: z24.string().min(1).max(128),
8464
- projectId: z24.string().min(1).max(128)
8979
+ z27.object({
8980
+ orgId: z27.string().min(1).max(128),
8981
+ projectId: z27.string().min(1).max(128)
8465
8982
  })
8466
8983
  ).mutation(async ({ input }) => {
8467
8984
  let res;
@@ -8474,8 +8991,8 @@ var cloudSyncRouter = router({
8474
8991
  accountId: await accountForOrgScope(input.orgId)
8475
8992
  });
8476
8993
  } catch (err) {
8477
- if (err instanceof TRPCError20 && err.code === "PRECONDITION_FAILED") {
8478
- throw new TRPCError20({ code: "PRECONDITION_FAILED", message: "CLOUD_NOT_LINKED" });
8994
+ if (err instanceof TRPCError22 && err.code === "PRECONDITION_FAILED") {
8995
+ throw new TRPCError22({ code: "PRECONDITION_FAILED", message: "CLOUD_NOT_LINKED" });
8479
8996
  }
8480
8997
  throw err;
8481
8998
  }
@@ -8483,12 +9000,12 @@ var cloudSyncRouter = router({
8483
9000
  const detail = await cloudMessage(res);
8484
9001
  if (res.status === 403) {
8485
9002
  const code = /not a member/i.test(detail) ? "PROJECT_NOT_MEMBER" : "PROJECT_DELETE_FORBIDDEN";
8486
- throw new TRPCError20({ code: "FORBIDDEN", message: code, cause: new Error(detail) });
9003
+ throw new TRPCError22({ code: "FORBIDDEN", message: code, cause: new Error(detail) });
8487
9004
  }
8488
9005
  if (res.status === 404) {
8489
- throw new TRPCError20({ code: "NOT_FOUND", message: "PROJECT_NOT_IN_ORG", cause: new Error(detail) });
9006
+ throw new TRPCError22({ code: "NOT_FOUND", message: "PROJECT_NOT_IN_ORG", cause: new Error(detail) });
8490
9007
  }
8491
- throw new TRPCError20({
9008
+ throw new TRPCError22({
8492
9009
  code: "BAD_GATEWAY",
8493
9010
  message: detail || `PROJECT_DELETE_FAILED (${res.status})`
8494
9011
  });
@@ -8517,10 +9034,10 @@ var cloudSyncRouter = router({
8517
9034
  * d'un autre. Même forme que `rememberOrgNames`, et pour la même raison :
8518
9035
  * ce que seul le cloud sait, on le recopie quand il le dit.
8519
9036
  */
8520
- rememberOrgAccounts: protectedProcedure.input(z24.object({
8521
- orgs: z24.array(z24.object({
8522
- orgId: z24.string().min(1).max(128),
8523
- accountId: z24.string().min(1).max(64)
9037
+ rememberOrgAccounts: protectedProcedure.input(z27.object({
9038
+ orgs: z27.array(z27.object({
9039
+ orgId: z27.string().min(1).max(128),
9040
+ accountId: z27.string().min(1).max(64)
8524
9041
  })).max(200)
8525
9042
  })).mutation(async ({ input }) => {
8526
9043
  const candidates = /* @__PURE__ */ new Map();
@@ -8547,10 +9064,10 @@ var cloudSyncRouter = router({
8547
9064
  }
8548
9065
  return { remembered };
8549
9066
  }),
8550
- rememberOrgNames: protectedProcedure.input(z24.object({
8551
- orgs: z24.array(z24.object({
8552
- orgId: z24.string().min(1).max(128),
8553
- name: z24.string().min(1).max(100)
9067
+ rememberOrgNames: protectedProcedure.input(z27.object({
9068
+ orgs: z27.array(z27.object({
9069
+ orgId: z27.string().min(1).max(128),
9070
+ name: z27.string().min(1).max(100)
8554
9071
  })).max(200)
8555
9072
  })).mutation(async ({ input }) => {
8556
9073
  for (const { orgId, name } of input.orgs) await rememberOrgName(orgId, name);
@@ -8580,7 +9097,7 @@ var cloudSyncRouter = router({
8580
9097
  * Un 404 efface le logo retenu — l'organisation en a retiré un — tandis qu'un
8581
9098
  * réseau muet laisse en place ce qui est déjà là : hors ligne, on garde.
8582
9099
  */
8583
- cacheOrgLogos: protectedProcedure.input(z24.object({ orgIds: z24.array(z24.string().min(1).max(128)).max(200) })).mutation(async ({ input }) => {
9100
+ cacheOrgLogos: protectedProcedure.input(z27.object({ orgIds: z27.array(z27.string().min(1).max(128)).max(200) })).mutation(async ({ input }) => {
8584
9101
  let cached = 0;
8585
9102
  for (const orgId of input.orgIds) {
8586
9103
  const base = await orgCloudUrl(orgId);
@@ -8637,22 +9154,22 @@ var cloudSyncRouter = router({
8637
9154
 
8638
9155
  // ../server/src/trpc/routers/filesystem.ts
8639
9156
  import { readdir as readdir4, stat as stat6 } from "fs/promises";
8640
- import { homedir as homedir3 } from "os";
8641
- import { isAbsolute as isAbsolute3, join as join12, resolve as resolve2, dirname as dirname7, basename } from "path";
8642
- import { z as z25 } from "zod";
9157
+ import { homedir as homedir4 } from "os";
9158
+ import { isAbsolute as isAbsolute3, join as join13, resolve as resolve3, dirname as dirname8, basename } from "path";
9159
+ import { z as z28 } from "zod";
8643
9160
  var MAX_ENTRIES2 = 500;
8644
- var PATH_SCHEMA = z25.string().min(1).max(4096);
9161
+ var PATH_SCHEMA = z28.string().min(1).max(4096);
8645
9162
  function safeResolve(input) {
8646
9163
  if (input.includes("\0")) throw new Error("Chemin invalide");
8647
- const expanded = input.startsWith("~") ? join12(homedir3(), input.slice(1)) : input;
9164
+ const expanded = input.startsWith("~") ? join13(homedir4(), input.slice(1)) : input;
8648
9165
  if (!isAbsolute3(expanded)) throw new Error("Le chemin doit \xEAtre absolu");
8649
- return resolve2(expanded);
9166
+ return resolve3(expanded);
8650
9167
  }
8651
9168
  var filesystemRouter = router({
8652
9169
  /** Where a picker should open when the user has chosen nothing yet. */
8653
9170
  suggestedRoots: protectedProcedure.query(async () => {
8654
9171
  const roots = [
8655
- { label: "home", path: homedir3() },
9172
+ { label: "home", path: homedir4() },
8656
9173
  // The launch directory, taken from the vault rather than the process.
8657
9174
  { label: "cwd", path: await resolveWorkspaceCwd({}) }
8658
9175
  ];
@@ -8665,7 +9182,7 @@ var filesystemRouter = router({
8665
9182
  * Hidden entries are skipped: they are noise for choosing a workspace, and
8666
9183
  * `.runeya` itself would invite placing a project inside another's data.
8667
9184
  */
8668
- listDirectories: protectedProcedure.input(z25.object({ path: PATH_SCHEMA })).query(async ({ input }) => {
9185
+ listDirectories: protectedProcedure.input(z28.object({ path: PATH_SCHEMA })).query(async ({ input }) => {
8669
9186
  const dir = safeResolve(input.path);
8670
9187
  const info = await stat6(dir).catch(() => null);
8671
9188
  if (!info?.isDirectory()) {
@@ -8680,13 +9197,13 @@ var filesystemRouter = router({
8680
9197
  const truncated = names.length > MAX_ENTRIES2;
8681
9198
  const entries2 = [];
8682
9199
  for (const name of names.slice(0, MAX_ENTRIES2)) {
8683
- const path = join12(dir, name);
9200
+ const path = join13(dir, name);
8684
9201
  entries2.push({ name, path, hasRuneyaProject: await holdsRuneyaProject(path) });
8685
9202
  }
8686
9203
  return {
8687
9204
  path: dir,
8688
9205
  // Null at the filesystem root, which is what stops "up" from looping.
8689
- parent: dirname7(dir) === dir ? null : dirname7(dir),
9206
+ parent: dirname8(dir) === dir ? null : dirname8(dir),
8690
9207
  name: basename(dir) || dir,
8691
9208
  entries: entries2,
8692
9209
  truncated,
@@ -8698,7 +9215,7 @@ var filesystemRouter = router({
8698
9215
  * qui s'y trouve, ou rien (déjà dans la liste, lié à un projet d'organisation,
8699
9216
  * racine machine).
8700
9217
  */
8701
- checkLocalProjectTarget: protectedProcedure.input(z25.object({ path: PATH_SCHEMA })).query(async ({ input }) => {
9218
+ checkLocalProjectTarget: protectedProcedure.input(z28.object({ path: PATH_SCHEMA })).query(async ({ input }) => {
8702
9219
  const dir = safeResolve(input.path);
8703
9220
  const info = await stat6(dir).catch(() => null);
8704
9221
  if (!info?.isDirectory()) return { path: dir, status: "missing", projectName: null };
@@ -8710,10 +9227,10 @@ var filesystemRouter = router({
8710
9227
  * Two projects in one directory is what makes services run against the wrong
8711
9228
  * code, so a directory that already holds one is refused outright.
8712
9229
  */
8713
- checkProjectTarget: protectedProcedure.input(z25.object({
9230
+ checkProjectTarget: protectedProcedure.input(z28.object({
8714
9231
  path: PATH_SCHEMA,
8715
- projectId: z25.string().min(1).max(128),
8716
- orgId: z25.string().max(128).nullable().optional()
9232
+ projectId: z28.string().min(1).max(128),
9233
+ orgId: z28.string().max(128).nullable().optional()
8717
9234
  })).query(async ({ input }) => {
8718
9235
  const dir = safeResolve(input.path);
8719
9236
  const info = await stat6(dir).catch(() => null);
@@ -8732,15 +9249,15 @@ var filesystemRouter = router({
8732
9249
  });
8733
9250
 
8734
9251
  // ../server/src/trpc/routers/desktop.ts
8735
- import { z as z26 } from "zod";
9252
+ import { z as z29 } from "zod";
8736
9253
  import { isAbsolute as isAbsolute5 } from "path";
8737
- import { TRPCError as TRPCError21 } from "@trpc/server";
9254
+ import { TRPCError as TRPCError23 } from "@trpc/server";
8738
9255
 
8739
9256
  // ../server/src/services/desktop-launcher.ts
8740
- import { execFile as execFile4 } from "child_process";
9257
+ import { execFile as execFile5 } from "child_process";
8741
9258
  import { isAbsolute as isAbsolute4 } from "path";
8742
9259
  import { promisify as promisify3 } from "util";
8743
- var exec = promisify3(execFile4);
9260
+ var exec = promisify3(execFile5);
8744
9261
  var INSTALL_TIMEOUT_MS2 = 3e4;
8745
9262
  var cli = null;
8746
9263
  function registerCli(location) {
@@ -8818,49 +9335,58 @@ var desktopRouter = router({
8818
9335
  * dessinée par le serveur. Sans cette annonce elle garderait la teinte figée
8819
9336
  * du fichier livré avec le CLI. Rien n'est persisté — voir `tray-accent.ts`.
8820
9337
  */
8821
- setTrayAccent: protectedProcedure.input(z26.object({
9338
+ setTrayAccent: protectedProcedure.input(z29.object({
8822
9339
  // `#rgb` ou `#rrggbb` : la forme est vérifiée ici, pas rabotée plus loin.
8823
- color: z26.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/).nullable()
9340
+ color: z29.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/).nullable()
8824
9341
  })).mutation(({ input }) => {
8825
9342
  setTrayAccent(input.color);
8826
9343
  return { ok: true };
8827
9344
  }),
8828
- installLauncher: protectedProcedure.input(z26.object({
8829
- name: z26.string().trim().min(1).max(120),
9345
+ installLauncher: protectedProcedure.input(z29.object({
9346
+ name: z29.string().trim().min(1).max(120),
8830
9347
  /**
8831
9348
  * `null` : raccourci global, sans dossier épinglé. Sinon un chemin
8832
9349
  * absolu, refusé autrement : le dossier devient un argument positionnel
8833
9350
  * du CLI, où une valeur commençant par un tiret passerait pour un drapeau.
8834
9351
  */
8835
- workingDir: z26.string().max(4096).refine(isAbsolute5, {
9352
+ workingDir: z29.string().max(4096).refine(isAbsolute5, {
8836
9353
  message: "Le dossier de travail doit \xEAtre un chemin absolu."
8837
9354
  }).nullable(),
8838
- port: z26.number().int().min(1).max(65535),
8839
- agentPort: z26.number().int().min(1).max(65535),
9355
+ port: z29.number().int().min(1).max(65535),
9356
+ agentPort: z29.number().int().min(1).max(65535),
8840
9357
  /** Projet ouvert directement par le raccourci (`--project`). Absent : liste des projets. */
8841
- projectId: z26.string().max(128).regex(LAUNCHER_PROJECT_ID_PATTERN).nullable().optional()
9358
+ projectId: z29.string().max(128).regex(LAUNCHER_PROJECT_ID_PATTERN).nullable().optional()
8842
9359
  })).mutation(async ({ input }) => {
8843
9360
  try {
8844
9361
  return await installLauncher(input);
8845
9362
  } catch (err) {
8846
9363
  const message = err.message;
8847
9364
  if (message === "launcher-working-dir-not-absolute") {
8848
- throw new TRPCError21({
9365
+ throw new TRPCError23({
8849
9366
  code: "BAD_REQUEST",
8850
9367
  message: "Le dossier de travail doit \xEAtre un chemin absolu."
8851
9368
  });
8852
9369
  }
8853
9370
  if (message === "launcher-unavailable") {
8854
- throw new TRPCError21({
9371
+ throw new TRPCError23({
8855
9372
  code: "PRECONDITION_FAILED",
8856
9373
  message: "Cette Runeya ne sait pas o\xF9 trouver son CLI : cr\xE9ez le raccourci avec `runeya install-desktop`."
8857
9374
  });
8858
9375
  }
8859
- throw new TRPCError21({ code: "INTERNAL_SERVER_ERROR", message });
9376
+ throw new TRPCError23({ code: "INTERNAL_SERVER_ERROR", message });
8860
9377
  }
8861
9378
  })
8862
9379
  });
8863
9380
 
9381
+ // ../server/src/trpc/routers/mcp-ready.ts
9382
+ import { z as z30 } from "zod";
9383
+ var mcpReadyRouter = router({
9384
+ signal: protectedProcedure.input(z30.object({ id: z30.string().min(1).max(128) })).mutation(({ input }) => {
9385
+ signalMcpReady(input.id);
9386
+ return { ok: true };
9387
+ })
9388
+ });
9389
+
8864
9390
  // ../server/src/trpc/router.ts
8865
9391
  var appRouter = router({
8866
9392
  health: healthRouter,
@@ -8868,6 +9394,9 @@ var appRouter = router({
8868
9394
  appUpdate: appUpdateRouter,
8869
9395
  changelog: changelogRouter,
8870
9396
  diagnostics: diagnosticsRouter,
9397
+ mcpPairing: mcpPairingRouter,
9398
+ mcpReady: mcpReadyRouter,
9399
+ aiClients: aiClientsRouter,
8871
9400
  project: projectRouter,
8872
9401
  service: serviceRouter,
8873
9402
  agent: agentRouter,
@@ -8956,8 +9485,8 @@ async function ensureFilePermissions() {
8956
9485
  }
8957
9486
 
8958
9487
  // ../server/src/services/vault-migration.ts
8959
- import { readdir as readdir5, readFile as readFile6, writeFile as writeFile7, mkdir as mkdir9, copyFile as copyFile2, unlink, rename as rename3, stat as stat8 } from "fs/promises";
8960
- import { join as join13 } from "path";
9488
+ import { readdir as readdir5, readFile as readFile8, writeFile as writeFile7, mkdir as mkdir10, copyFile as copyFile2, unlink, rename as rename3, stat as stat8 } from "fs/promises";
9489
+ import { join as join14 } from "path";
8961
9490
  var KEY_FILES = [".encryption.key.enc", ".runeya-verify"];
8962
9491
  var MACHINE_FILES = [
8963
9492
  "agents.json",
@@ -8987,17 +9516,17 @@ async function openLegacyVault(legacyRoot, masterPassword) {
8987
9516
  }
8988
9517
  async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machineKey, now) {
8989
9518
  const stamp = now.toISOString().replace(/[:.]/g, "-").substring(0, 19);
8990
- const backupDir = join13(legacyRoot, `backup-${stamp}`);
8991
- await mkdir9(backupDir, { recursive: true });
9519
+ const backupDir = join14(legacyRoot, `backup-${stamp}`);
9520
+ await mkdir10(backupDir, { recursive: true });
8992
9521
  const jsonFiles = await vaultJsonFiles(legacyRoot);
8993
9522
  for (const name of [...jsonFiles, ...KEY_FILES]) {
8994
- if (await exists2(join13(legacyRoot, name))) {
8995
- await copyFile2(join13(legacyRoot, name), join13(backupDir, name));
9523
+ if (await exists2(join14(legacyRoot, name))) {
9524
+ await copyFile2(join14(legacyRoot, name), join14(backupDir, name));
8996
9525
  }
8997
9526
  }
8998
9527
  const rekeyed = [];
8999
9528
  for (const name of jsonFiles) {
9000
- const raw = await readFile6(join13(legacyRoot, name), "utf-8");
9529
+ const raw = await readFile8(join14(legacyRoot, name), "utf-8");
9001
9530
  let parsed;
9002
9531
  try {
9003
9532
  parsed = JSON.parse(raw);
@@ -9007,13 +9536,13 @@ async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machine
9007
9536
  const counter = { n: 0 };
9008
9537
  const converted = rekeyValue(parsed, legacyKey, machineKey, counter);
9009
9538
  if (counter.n === 0) continue;
9010
- const tmp = join13(legacyRoot, `${name}.tmp`);
9539
+ const tmp = join14(legacyRoot, `${name}.tmp`);
9011
9540
  await writeFile7(tmp, JSON.stringify(converted, null, 2), "utf-8");
9012
- await rename3(tmp, join13(legacyRoot, name));
9541
+ await rename3(tmp, join14(legacyRoot, name));
9013
9542
  rekeyed.push({ file: name, values: counter.n });
9014
9543
  }
9015
9544
  for (const { file, values } of rekeyed) {
9016
- const parsed = JSON.parse(await readFile6(join13(legacyRoot, file), "utf-8"));
9545
+ const parsed = JSON.parse(await readFile8(join14(legacyRoot, file), "utf-8"));
9017
9546
  const check = { n: 0 };
9018
9547
  rekeyValue(parsed, machineKey, machineKey, check);
9019
9548
  if (check.n !== values) {
@@ -9022,13 +9551,13 @@ async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machine
9022
9551
  );
9023
9552
  }
9024
9553
  }
9025
- await mkdir9(machineRootDir, { recursive: true });
9554
+ await mkdir10(machineRootDir, { recursive: true });
9026
9555
  const moved = [];
9027
9556
  const skipped = [];
9028
9557
  for (const name of MACHINE_FILES) {
9029
- const from = join13(legacyRoot, name);
9558
+ const from = join14(legacyRoot, name);
9030
9559
  if (!await exists2(from)) continue;
9031
- const to = join13(machineRootDir, name);
9560
+ const to = join14(machineRootDir, name);
9032
9561
  if (await exists2(to)) {
9033
9562
  skipped.push(name);
9034
9563
  continue;
@@ -9038,15 +9567,15 @@ async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machine
9038
9567
  moved.push(name);
9039
9568
  }
9040
9569
  for (const name of KEY_FILES) {
9041
- const path = join13(legacyRoot, name);
9570
+ const path = join14(legacyRoot, name);
9042
9571
  if (await exists2(path)) await unlink(path);
9043
9572
  }
9044
9573
  return { backupDir, rekeyed, moved, skipped };
9045
9574
  }
9046
9575
 
9047
9576
  // ../server/src/services/settings-migration.ts
9048
- import { readFile as readFile7, writeFile as writeFile8, rename as rename4, unlink as unlink2, mkdir as mkdir10, chmod as chmod4, stat as stat9 } from "fs/promises";
9049
- import { join as join14 } from "path";
9577
+ import { readFile as readFile9, writeFile as writeFile8, rename as rename4, unlink as unlink2, mkdir as mkdir11, chmod as chmod4, stat as stat9 } from "fs/promises";
9578
+ import { join as join15 } from "path";
9050
9579
  var SETTINGS_FILE = "settings.json";
9051
9580
  var SETTINGS_PRIVATE_FILE = "settings.private.json";
9052
9581
  async function exists3(path) {
@@ -9059,9 +9588,9 @@ async function legacySource(filename) {
9059
9588
  return null;
9060
9589
  }
9061
9590
  async function moveInto(root, filename, from) {
9062
- await mkdir10(root, { recursive: true });
9063
- const to = join14(root, filename);
9064
- const contents = await readFile7(from, "utf-8");
9591
+ await mkdir11(root, { recursive: true });
9592
+ const to = join15(root, filename);
9593
+ const contents = await readFile9(from, "utf-8");
9065
9594
  const tmp = to + ".tmp";
9066
9595
  await writeFile8(tmp, contents, "utf-8");
9067
9596
  await rename4(tmp, to);
@@ -9085,7 +9614,7 @@ async function migrateSettingsIntoVaults() {
9085
9614
  const orgId = orgs[0];
9086
9615
  const root = orgVaultRoot(orgId);
9087
9616
  for (const { filename, from } of sources) {
9088
- if (await exists3(join14(root, filename))) continue;
9617
+ if (await exists3(join15(root, filename))) continue;
9089
9618
  await moveInto(root, filename, from);
9090
9619
  report.files.push(filename);
9091
9620
  }
@@ -9098,17 +9627,17 @@ async function migrateSettingsIntoVaults() {
9098
9627
 
9099
9628
  // ../server/src/migrations/migrate-from-legacy.ts
9100
9629
  import { createRequire } from "module";
9101
- import { randomBytes as randomBytes3, createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3 } from "crypto";
9102
- import { randomUUID as randomUUID8 } from "crypto";
9630
+ import { randomBytes as randomBytes4, createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3 } from "crypto";
9631
+ import { randomUUID as randomUUID9 } from "crypto";
9103
9632
  import {
9104
- readFile as readFile8,
9633
+ readFile as readFile10,
9105
9634
  writeFile as writeFile9,
9106
9635
  rename as rename5,
9107
- mkdir as mkdir11,
9636
+ mkdir as mkdir12,
9108
9637
  access as access2,
9109
9638
  readdir as readdir6
9110
9639
  } from "fs/promises";
9111
- import { join as join15, dirname as dirname8 } from "path";
9640
+ import { join as join16, dirname as dirname9 } from "path";
9112
9641
  var _require = createRequire(import.meta.url);
9113
9642
  var PARSER_ID_MAP = {
9114
9643
  "stack-monitor-parser-jsons": "native:json",
@@ -9121,7 +9650,7 @@ var PARSER_ID_MAP = {
9121
9650
  function encryptValue2(plaintext) {
9122
9651
  const key = getEncryptionKey();
9123
9652
  if (!key) throw new Error("[legacy-migration] Encryption key not loaded");
9124
- const iv = randomBytes3(16);
9653
+ const iv = randomBytes4(16);
9125
9654
  const cipher = createCipheriv3("aes-256-gcm", key, iv);
9126
9655
  let enc = cipher.update(plaintext, "utf-8", "hex");
9127
9656
  enc += cipher.final("hex");
@@ -9225,7 +9754,7 @@ async function getSodium() {
9225
9754
  return _sodium;
9226
9755
  }
9227
9756
  async function decryptLegacyFile(filePath, key, sodium) {
9228
- const raw = (await readFile8(filePath, "utf-8")).trim();
9757
+ const raw = (await readFile10(filePath, "utf-8")).trim();
9229
9758
  if (!raw || raw === "[]" || raw === "{}") {
9230
9759
  try {
9231
9760
  return JSON.parse(raw);
@@ -9250,7 +9779,7 @@ async function atomicWrite(path, data) {
9250
9779
  }
9251
9780
  async function runLegacyMigration(dataDir) {
9252
9781
  const legacyRoot = env.DATA_DIR;
9253
- const legacyKeyPath = join15(legacyRoot, "dbs", "encryption-key.json");
9782
+ const legacyKeyPath = join16(legacyRoot, "dbs", "encryption-key.json");
9254
9783
  try {
9255
9784
  await access2(legacyKeyPath);
9256
9785
  } catch {
@@ -9260,18 +9789,18 @@ async function runLegacyMigration(dataDir) {
9260
9789
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
9261
9790
  const legacyTmp = `${legacyRoot}-tmp`;
9262
9791
  await rename5(legacyRoot, legacyTmp);
9263
- await mkdir11(dataDir, { recursive: true });
9264
- const legacyOld = join15(dataDir, `backup-${timestamp}`);
9792
+ await mkdir12(dataDir, { recursive: true });
9793
+ const legacyOld = join16(dataDir, `backup-${timestamp}`);
9265
9794
  await rename5(legacyTmp, legacyOld);
9266
9795
  console.log(`[legacy-migration] Legacy data backed up to ${legacyOld}`);
9267
- const keyJson = JSON.parse(await readFile8(join15(legacyOld, "dbs", "encryption-key.json"), "utf-8"));
9796
+ const keyJson = JSON.parse(await readFile10(join16(legacyOld, "dbs", "encryption-key.json"), "utf-8"));
9268
9797
  const sodium = await getSodium();
9269
9798
  const sodiumKey = sodium.from_base64(keyJson.encryptionKey);
9270
9799
  const keyBuffer = Buffer.from(sodiumKey);
9271
9800
  const runyeaKeyHex = keyBuffer.toString("hex");
9272
9801
  await setEncryptionKey(runyeaKeyHex);
9273
9802
  console.log("[legacy-migration] Encryption key loaded in memory (temporary)");
9274
- const dbsDir = join15(legacyOld, "dbs");
9803
+ const dbsDir = join16(legacyOld, "dbs");
9275
9804
  async function readEncrypted(filePath) {
9276
9805
  try {
9277
9806
  return await decryptLegacyFile(filePath, sodiumKey, sodium);
@@ -9280,29 +9809,29 @@ async function runLegacyMigration(dataDir) {
9280
9809
  return null;
9281
9810
  }
9282
9811
  }
9283
- const envsDir = join15(dbsDir, "envs");
9812
+ const envsDir = join16(dbsDir, "envs");
9284
9813
  const envFiles = (await readdir6(envsDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
9285
9814
  const legacyEnvs = {};
9286
9815
  for (const file of envFiles) {
9287
9816
  const name = file.replace(".encrypted.json", "");
9288
- legacyEnvs[name] = await readEncrypted(join15(envsDir, file));
9817
+ legacyEnvs[name] = await readEncrypted(join16(envsDir, file));
9289
9818
  }
9290
- const servicesDir = join15(dbsDir, "services");
9819
+ const servicesDir = join16(dbsDir, "services");
9291
9820
  const serviceFiles = (await readdir6(servicesDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
9292
9821
  const legacyServices = {};
9293
9822
  for (const file of serviceFiles) {
9294
9823
  const name = file.replace(".encrypted.json", "");
9295
9824
  if (!name) continue;
9296
- const data = await readEncrypted(join15(servicesDir, file));
9825
+ const data = await readEncrypted(join16(servicesDir, file));
9297
9826
  if (data?.label) legacyServices[name] = data;
9298
9827
  }
9299
- const overridesDir = join15(dbsDir, "overrides");
9828
+ const overridesDir = join16(dbsDir, "overrides");
9300
9829
  const overrideFiles = (await readdir6(overridesDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
9301
9830
  const legacyServiceOverrides = {};
9302
9831
  const legacyEnvOverrides = {};
9303
9832
  for (const file of overrideFiles) {
9304
9833
  const baseName = file.replace(".encrypted.json", "");
9305
- const data = await readEncrypted(join15(overridesDir, file));
9834
+ const data = await readEncrypted(join16(overridesDir, file));
9306
9835
  if (!data) continue;
9307
9836
  if (baseName.endsWith("-envs")) {
9308
9837
  legacyServiceOverrides[baseName.slice(0, -5)] = data;
@@ -9310,18 +9839,18 @@ async function runLegacyMigration(dataDir) {
9310
9839
  legacyEnvOverrides[baseName.slice(0, -12)] = data;
9311
9840
  }
9312
9841
  }
9313
- const parsersDir = join15(dbsDir, "parsers");
9842
+ const parsersDir = join16(dbsDir, "parsers");
9314
9843
  const parserFiles = (await readdir6(parsersDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
9315
9844
  const legacyParsers = [];
9316
9845
  for (const file of parserFiles) {
9317
- const data = await readEncrypted(join15(parsersDir, file));
9846
+ const data = await readEncrypted(join16(parsersDir, file));
9318
9847
  if (data?.id) legacyParsers.push(data);
9319
9848
  }
9320
- const projectMonoId = randomUUID8();
9849
+ const projectMonoId = randomUUID9();
9321
9850
  const envIdMap = {};
9322
9851
  const labelToKey = {};
9323
9852
  for (const [name, legacyEnv] of Object.entries(legacyEnvs)) {
9324
- envIdMap[name] = randomUUID8();
9853
+ envIdMap[name] = randomUUID9();
9325
9854
  if (legacyEnv?.label) labelToKey[legacyEnv.label] = name;
9326
9855
  }
9327
9856
  const runeEnvs = [];
@@ -9346,7 +9875,7 @@ async function runLegacyMigration(dataDir) {
9346
9875
  if (hasInvalidInterpolation(value)) continue;
9347
9876
  const cleaned = cleanVarKey(key);
9348
9877
  if (inheritedVars[cleaned] === value) continue;
9349
- const varId = randomUUID8();
9878
+ const varId = randomUUID9();
9350
9879
  variables[varId] = makeVariable(cleaned, value);
9351
9880
  varIdLookup.set(`${envIdMap[name]}:${cleaned}`, varId);
9352
9881
  }
@@ -9367,7 +9896,7 @@ async function runLegacyMigration(dataDir) {
9367
9896
  const serviceIdMap = {};
9368
9897
  const runeServices = [];
9369
9898
  for (const [name, legacy] of Object.entries(legacyServices)) {
9370
- const id = randomUUID8();
9899
+ const id = randomUUID9();
9371
9900
  serviceIdMap[name] = id;
9372
9901
  const rootPath = legacy.rootPath || "";
9373
9902
  const rootPathCwd = rootPath && rootPath !== "." ? rootPath : "";
@@ -9376,7 +9905,7 @@ async function runLegacyMigration(dataDir) {
9376
9905
  const parts = [cmd.spawnCmd, ...cmd.spawnArgs || []].filter(Boolean);
9377
9906
  if (!parts.length) continue;
9378
9907
  commands.push({
9379
- id: cmd.id || randomUUID8(),
9908
+ id: cmd.id || randomUUID9(),
9380
9909
  label: "Launch",
9381
9910
  command: parts.join(" "),
9382
9911
  cwd: cmd.spawnOptions?.cwd || "",
@@ -9437,7 +9966,7 @@ async function runLegacyMigration(dataDir) {
9437
9966
  const legacyBootstrapCmds = container.bootstrap?.commands || [];
9438
9967
  if (legacyBootstrapCmds.length > 0) {
9439
9968
  dockerConfig.bootstrapCommands = legacyBootstrapCmds.filter((bs) => bs.cmd || bs.entrypoint).map((bs, idx) => ({
9440
- id: bs.id || randomUUID8(),
9969
+ id: bs.id || randomUUID9(),
9441
9970
  label: bs.label || `Bootstrap ${idx + 1}`,
9442
9971
  command: bs.cmd || "",
9443
9972
  ...bs.entrypoint ? { entrypoint: bs.entrypoint } : {},
@@ -9485,7 +10014,7 @@ async function runLegacyMigration(dataDir) {
9485
10014
  cwd: serviceCwd,
9486
10015
  runner: dockerConfig ? "docker" : "native",
9487
10016
  shortcuts: (legacy.shortcuts || []).filter((s) => s.spawnCmd || s.label).map((s) => ({
9488
- id: s.id || randomUUID8(),
10017
+ id: s.id || randomUUID9(),
9489
10018
  label: s.label || s.spawnCmd || "",
9490
10019
  command: [s.spawnCmd, ...s.spawnArgs || []].filter(Boolean).join(" "),
9491
10020
  ...s.spawnOptions?.cwd ? { cwd: s.spawnOptions.cwd } : {}
@@ -9525,7 +10054,7 @@ async function runLegacyMigration(dataDir) {
9525
10054
  );
9526
10055
  if (!svcScopedEnv) {
9527
10056
  svcScopedEnv = {
9528
- id: randomUUID8(),
10057
+ id: randomUUID9(),
9529
10058
  projectId: projectMonoId,
9530
10059
  name: `${service?.name || name} (${globalEnv.name})`,
9531
10060
  scope: "service",
@@ -9549,7 +10078,7 @@ async function runLegacyMigration(dataDir) {
9549
10078
  const varPost = typeof varData === "object" ? String(varData.suffix ?? "") : "";
9550
10079
  if (!varKey || existingKeys.has(cleanVarKey(varKey))) continue;
9551
10080
  const cleaned = cleanVarKey(varKey);
9552
- const varId = randomUUID8();
10081
+ const varId = randomUUID9();
9553
10082
  svcScopedEnv.variables[varId] = makeVariable(cleaned, varValue, varPre, varPost);
9554
10083
  existingKeys.add(cleaned);
9555
10084
  }
@@ -9569,7 +10098,7 @@ async function runLegacyMigration(dataDir) {
9569
10098
  );
9570
10099
  if (!svcScopedEnv) {
9571
10100
  svcScopedEnv = {
9572
- id: randomUUID8(),
10101
+ id: randomUUID9(),
9573
10102
  projectId: projectMonoId,
9574
10103
  name: `${service?.name || serviceName} (${globalEnv.name})`,
9575
10104
  scope: "service",
@@ -9590,7 +10119,7 @@ async function runLegacyMigration(dataDir) {
9590
10119
  const baseEntry = Object.entries(svcScopedEnv.variables).find(([, v]) => v.key === cleaned);
9591
10120
  let varId = baseEntry?.[0];
9592
10121
  if (!varId) {
9593
- varId = randomUUID8();
10122
+ varId = randomUUID9();
9594
10123
  svcScopedEnv.variables[varId] = {
9595
10124
  key: cleaned,
9596
10125
  value: { value: "", pre: "", post: "", isSecret: false, shareWithAi: false, injectIntoAi: false, required: false, overrideOnly: false, description: "" }
@@ -9620,13 +10149,13 @@ async function runLegacyMigration(dataDir) {
9620
10149
  flatOverrides[varId] = makeVariable(cleaned, value);
9621
10150
  }
9622
10151
  } else {
9623
- flatOverrides[randomUUID8()] = makeVariable(cleaned, value);
10152
+ flatOverrides[randomUUID9()] = makeVariable(cleaned, value);
9624
10153
  }
9625
10154
  } else {
9626
10155
  const cleaned = cleanVarKey(key);
9627
10156
  const existingVarId = Object.entries(env2.variables).find(([, v]) => v.key === cleaned)?.[0];
9628
10157
  if (!existingVarId) {
9629
- const varId = randomUUID8();
10158
+ const varId = randomUUID9();
9630
10159
  env2.variables[varId] = makeVariable(cleaned, value);
9631
10160
  varIdLookup.set(`${envId}:${cleaned}`, varId);
9632
10161
  }
@@ -9655,7 +10184,7 @@ async function runLegacyMigration(dataDir) {
9655
10184
  }
9656
10185
  let existingSettings = {};
9657
10186
  try {
9658
- existingSettings = JSON.parse(await readFile8(join15(dataDir, "settings.json"), "utf-8"));
10187
+ existingSettings = JSON.parse(await readFile10(join16(dataDir, "settings.json"), "utf-8"));
9659
10188
  } catch {
9660
10189
  }
9661
10190
  const migratedParsers = legacyParsers.map((p) => ({
@@ -9676,11 +10205,11 @@ ${p.transform ?? ""}`,
9676
10205
  ...migratedParsers
9677
10206
  ]
9678
10207
  };
9679
- await atomicWrite(join15(dataDir, "projects.json"), runeProjects);
9680
- await atomicWrite(join15(dataDir, "services.json"), runeServices);
9681
- await atomicWrite(join15(dataDir, "environments.json"), runeEnvs);
9682
- await atomicWrite(join15(dataDir, "environment-overrides.json"), flatOverrides);
9683
- await atomicWrite(join15(dataDir, "settings.json"), newSettings);
10208
+ await atomicWrite(join16(dataDir, "projects.json"), runeProjects);
10209
+ await atomicWrite(join16(dataDir, "services.json"), runeServices);
10210
+ await atomicWrite(join16(dataDir, "environments.json"), runeEnvs);
10211
+ await atomicWrite(join16(dataDir, "environment-overrides.json"), flatOverrides);
10212
+ await atomicWrite(join16(dataDir, "settings.json"), newSettings);
9684
10213
  const svcEnvCount = runeEnvs.filter((e) => e.scope === "service").length;
9685
10214
  console.log(`[legacy-migration] Written: ${runeProjects.length} projects, ${runeServices.length} services, ${runeEnvs.length} environments (${svcEnvCount} service-scoped)`);
9686
10215
  console.log(`[legacy-migration] Overrides: ${Object.keys(flatOverrides).length}, custom parsers: ${migratedParsers.length} (all disabled \u2014 rewrite required)`);
@@ -9694,9 +10223,9 @@ async function findLegacyKeyInBackup(dataDir) {
9694
10223
  const entries2 = await readdir6(dataDir);
9695
10224
  const backupDirs = entries2.filter((e) => e.startsWith("backup-")).sort((a, b) => a < b ? -1 : a > b ? 1 : 0).reverse();
9696
10225
  for (const dir of backupDirs) {
9697
- const keyJsonPath = join15(dataDir, dir, "dbs", "encryption-key.json");
10226
+ const keyJsonPath = join16(dataDir, dir, "dbs", "encryption-key.json");
9698
10227
  try {
9699
- const raw = await readFile8(keyJsonPath, "utf-8");
10228
+ const raw = await readFile10(keyJsonPath, "utf-8");
9700
10229
  const parsed = JSON.parse(raw);
9701
10230
  if (!parsed.encryptionKey) continue;
9702
10231
  const sodium = await getSodium();
@@ -9714,11 +10243,11 @@ async function findLegacyKeyInBackup(dataDir) {
9714
10243
  return null;
9715
10244
  }
9716
10245
  async function runOverrideOnlyMigration(dataDir) {
9717
- const { resolve: resolve4 } = await import("path");
9718
- const abDataDir = resolve4(dataDir);
10246
+ const { resolve: resolve5 } = await import("path");
10247
+ const abDataDir = resolve5(dataDir);
9719
10248
  const candidateDirs = [
9720
- join15(dirname8(env.DATA_DIR), ".runeya", "dbs", "overrides"),
9721
- join15(abDataDir, "dbs", "overrides")
10249
+ join16(dirname9(env.DATA_DIR), ".runeya", "dbs", "overrides"),
10250
+ join16(abDataDir, "dbs", "overrides")
9722
10251
  ];
9723
10252
  let overridesDir = null;
9724
10253
  let overrideFiles = [];
@@ -9734,7 +10263,7 @@ async function runOverrideOnlyMigration(dataDir) {
9734
10263
  }
9735
10264
  }
9736
10265
  if (!overridesDir || overrideFiles.length === 0) return false;
9737
- const legacyKeyPath = join15(overridesDir, "..", "encryption-key.json");
10266
+ const legacyKeyPath = join16(overridesDir, "..", "encryption-key.json");
9738
10267
  try {
9739
10268
  await access2(legacyKeyPath);
9740
10269
  return false;
@@ -9752,7 +10281,7 @@ async function runOverrideOnlyMigration(dataDir) {
9752
10281
  const envNameToId = {};
9753
10282
  let envsRaw;
9754
10283
  try {
9755
- envsRaw = JSON.parse(await readFile8(join15(abDataDir, "environments.json"), "utf-8"));
10284
+ envsRaw = JSON.parse(await readFile10(join16(abDataDir, "environments.json"), "utf-8"));
9756
10285
  for (const _env of envsRaw) {
9757
10286
  const env2 = _env;
9758
10287
  if (env2["name"] && env2["id"]) {
@@ -9771,7 +10300,7 @@ async function runOverrideOnlyMigration(dataDir) {
9771
10300
  let servicesRaw = [];
9772
10301
  const serviceNameToId = {};
9773
10302
  try {
9774
- servicesRaw = JSON.parse(await readFile8(join15(abDataDir, "services.json"), "utf-8"));
10303
+ servicesRaw = JSON.parse(await readFile10(join16(abDataDir, "services.json"), "utf-8"));
9775
10304
  for (const _svc of servicesRaw) {
9776
10305
  const svc = _svc;
9777
10306
  if (svc["name"] && svc["id"]) serviceNameToId[svc["name"]] = svc["id"];
@@ -9788,7 +10317,7 @@ async function runOverrideOnlyMigration(dataDir) {
9788
10317
  const envName = baseName.slice(0, -"-environment".length);
9789
10318
  let data;
9790
10319
  try {
9791
- data = await decryptLegacyFile(join15(overridesDir, file), sodiumKey, sodium);
10320
+ data = await decryptLegacyFile(join16(overridesDir, file), sodiumKey, sodium);
9792
10321
  } catch (err) {
9793
10322
  console.warn(`[override-migration] Failed to decrypt ${file}: ${err.message}`);
9794
10323
  continue;
@@ -9810,12 +10339,12 @@ async function runOverrideOnlyMigration(dataDir) {
9810
10339
  if (isSameAsBase(value, baseVar)) continue;
9811
10340
  flatOverrides[varId] = makeVariable(cleaned, value);
9812
10341
  } else {
9813
- flatOverrides[randomUUID8()] = makeVariable(cleaned, value);
10342
+ flatOverrides[randomUUID9()] = makeVariable(cleaned, value);
9814
10343
  }
9815
10344
  }
9816
10345
  }
9817
10346
  if (Object.keys(flatOverrides).length > 0) {
9818
- await atomicWrite(join15(abDataDir, "environment-overrides.json"), flatOverrides);
10347
+ await atomicWrite(join16(abDataDir, "environment-overrides.json"), flatOverrides);
9819
10348
  console.log(`[override-migration] Written ${Object.keys(flatOverrides).length} override(s) to environment-overrides.json`);
9820
10349
  }
9821
10350
  let envsModified = false;
@@ -9825,7 +10354,7 @@ async function runOverrideOnlyMigration(dataDir) {
9825
10354
  const serviceName = baseName.slice(0, -"-envs".length);
9826
10355
  let data;
9827
10356
  try {
9828
- data = await decryptLegacyFile(join15(overridesDir, file), sodiumKey, sodium);
10357
+ data = await decryptLegacyFile(join16(overridesDir, file), sodiumKey, sodium);
9829
10358
  } catch (err) {
9830
10359
  console.warn(`[override-migration] Failed to decrypt ${file}: ${err.message}`);
9831
10360
  continue;
@@ -9847,7 +10376,7 @@ async function runOverrideOnlyMigration(dataDir) {
9847
10376
  );
9848
10377
  if (!svcScopedEnv) {
9849
10378
  svcScopedEnv = {
9850
- id: randomUUID8(),
10379
+ id: randomUUID9(),
9851
10380
  projectId: globalEnv["projectId"],
9852
10381
  name: `${service?.["name"] || serviceName} (${globalEnv["name"]})`,
9853
10382
  scope: "service",
@@ -9869,7 +10398,7 @@ async function runOverrideOnlyMigration(dataDir) {
9869
10398
  const baseEntry = Object.entries(svcScopedEnv["variables"]).find(([, v]) => v.key === cleaned);
9870
10399
  let varId = baseEntry?.[0];
9871
10400
  if (!varId) {
9872
- varId = randomUUID8();
10401
+ varId = randomUUID9();
9873
10402
  svcScopedEnv["variables"][varId] = {
9874
10403
  key: cleaned,
9875
10404
  value: { value: "", pre: "", post: "", isSecret: false, shareWithAi: false, injectIntoAi: false, required: false, overrideOnly: false, description: "" }
@@ -9884,7 +10413,7 @@ async function runOverrideOnlyMigration(dataDir) {
9884
10413
  }
9885
10414
  }
9886
10415
  if (envsModified) {
9887
- await atomicWrite(join15(abDataDir, "environments.json"), envsRaw);
10416
+ await atomicWrite(join16(abDataDir, "environments.json"), envsRaw);
9888
10417
  console.log("[override-migration] Merged service overrides into environments.json");
9889
10418
  }
9890
10419
  const migratedDir = overridesDir.replace(/overrides$/, "overrides-migrated");
@@ -9899,7 +10428,7 @@ async function runOverrideOnlyMigration(dataDir) {
9899
10428
  }
9900
10429
 
9901
10430
  // ../server/src/services/server-state.ts
9902
- import { join as join16 } from "path";
10431
+ import { join as join17 } from "path";
9903
10432
  import { stat as stat10 } from "fs/promises";
9904
10433
  var ENCRYPTED_KEY_FILENAME2 = ".encryption.key.enc";
9905
10434
  var VERIFY_FILENAME2 = ".runeya-verify";
@@ -9909,22 +10438,22 @@ async function getServerState() {
9909
10438
  let hasVerify = false;
9910
10439
  let hasData = false;
9911
10440
  try {
9912
- await stat10(join16(dataDir, ENCRYPTED_KEY_FILENAME2));
10441
+ await stat10(join17(dataDir, ENCRYPTED_KEY_FILENAME2));
9913
10442
  hasEncFile = true;
9914
10443
  } catch {
9915
10444
  }
9916
10445
  try {
9917
- await stat10(join16(dataDir, VERIFY_FILENAME2));
10446
+ await stat10(join17(dataDir, VERIFY_FILENAME2));
9918
10447
  hasVerify = true;
9919
10448
  } catch {
9920
10449
  }
9921
10450
  try {
9922
- await stat10(join16(dataDir, "projects.json"));
10451
+ await stat10(join17(dataDir, "projects.json"));
9923
10452
  hasData = true;
9924
10453
  } catch {
9925
10454
  }
9926
10455
  try {
9927
- await stat10(join16(dataDir, "project.json"));
10456
+ await stat10(join17(dataDir, "project.json"));
9928
10457
  hasData = true;
9929
10458
  } catch {
9930
10459
  }
@@ -10230,7 +10759,7 @@ function getOpenApiDocument() {
10230
10759
 
10231
10760
  // ../server/src/create-server.ts
10232
10761
  import rateLimit from "express-rate-limit";
10233
- import { existsSync as existsSync2, createReadStream } from "fs";
10762
+ import { existsSync as existsSync3, createReadStream } from "fs";
10234
10763
  import multer from "multer";
10235
10764
 
10236
10765
  // ../server/src/services/image-cleanup.service.ts
@@ -10327,6 +10856,7 @@ var llmsLimiter = rateLimit({
10327
10856
  async function createLocalServer() {
10328
10857
  installLogCapture();
10329
10858
  generateJwtSecret();
10859
+ setMcpClientCheck((clientId) => mcpPairing.isPaired(clientId));
10330
10860
  const ciMode = await loadFromEnv();
10331
10861
  await runLegacyMigration(env.DATA_DIR);
10332
10862
  const keyDir = await keyRoot();
@@ -10345,10 +10875,10 @@ async function createLocalServer() {
10345
10875
  console.log("[server] Premier lancement \u2014 setup requis");
10346
10876
  }
10347
10877
  }
10348
- const absoluteDataDir = resolve3(env.DATA_DIR);
10878
+ const absoluteDataDir = resolve4(env.DATA_DIR);
10349
10879
  console.log(`[server] Local project directory: ${absoluteDataDir}`);
10350
10880
  console.log(`[server] Machine directory: ${machineRoot()}`);
10351
- if (existsSync2(env.DATA_DIR)) {
10881
+ if (existsSync3(env.DATA_DIR)) {
10352
10882
  await ensureDataGitignore(env.DATA_DIR);
10353
10883
  }
10354
10884
  await settingsManager.getSettings();
@@ -10436,7 +10966,7 @@ async function createLocalServer() {
10436
10966
  machineRoot(),
10437
10967
  ...orgRoots,
10438
10968
  ...vaultRoots,
10439
- ...[machineRoot(), ...vaultRoots].map((root) => join17(root, CONVERSATIONS_DIRNAME))
10969
+ ...[machineRoot(), ...vaultRoots].map((root) => join18(root, CONVERSATIONS_DIRNAME))
10440
10970
  ]);
10441
10971
  if (removed > 0) console.log(`[server] ${removed} fichier(s) temporaire(s) orphelin(s) retir\xE9(s)`);
10442
10972
  })().catch((err) => console.warn("[server] Temp file sweep failed:", err));
@@ -10697,13 +11227,14 @@ async function createLocalServer() {
10697
11227
  app.post("/api/auth/refresh", refreshLimiter, async (req, res) => {
10698
11228
  const auth = req.headers.authorization;
10699
11229
  const jwt = typeof auth === "string" && auth.startsWith("Bearer ") ? auth.slice(7) : "";
11230
+ let mcpClientId;
10700
11231
  try {
10701
- await verifyJwt(jwt);
11232
+ ({ mcpClientId } = await verifyJwt(jwt, { allowMcp: true }));
10702
11233
  } catch {
10703
11234
  res.status(401).json({ error: "Session invalide" });
10704
11235
  return;
10705
11236
  }
10706
- res.json({ token: await signJwt() });
11237
+ res.json({ token: await signJwt(mcpClientId ? { mcpClientId, ttl: MCP_TOKEN_TTL } : {}) });
10707
11238
  });
10708
11239
  app.post("/api/unlock", authLimiter, csrfMiddleware, express.json({ limit: "10kb" }), async (req, res) => {
10709
11240
  try {
@@ -10761,8 +11292,8 @@ async function createLocalServer() {
10761
11292
  }
10762
11293
  encKey = Buffer.from(encryptionKeyHex, "hex");
10763
11294
  } else {
10764
- const { randomBytes: randomBytes4 } = await import("crypto");
10765
- encKey = randomBytes4(32);
11295
+ const { randomBytes: randomBytes5 } = await import("crypto");
11296
+ encKey = randomBytes5(32);
10766
11297
  }
10767
11298
  }
10768
11299
  await writeEncryptedKey(await keyRoot(), masterPassword, encKey);
@@ -11084,16 +11615,16 @@ async function createLocalServer() {
11084
11615
  ]);
11085
11616
  server.closeAllConnections();
11086
11617
  await Promise.race([
11087
- new Promise((resolve4) => wss.close(() => resolve4())),
11088
- new Promise((resolve4) => setTimeout(resolve4, 2e3))
11618
+ new Promise((resolve5) => wss.close(() => resolve5())),
11619
+ new Promise((resolve5) => setTimeout(resolve5, 2e3))
11089
11620
  ]);
11090
11621
  await Promise.race([
11091
- new Promise((resolve4) => server.close(() => resolve4())),
11092
- new Promise((resolve4) => setTimeout(resolve4, 2e3))
11622
+ new Promise((resolve5) => server.close(() => resolve5())),
11623
+ new Promise((resolve5) => setTimeout(resolve5, 2e3))
11093
11624
  ]);
11094
11625
  console.log("[server] Shutdown complete");
11095
11626
  };
11096
- const { initCompanion } = await import("./companion-HBYSY2D5.js");
11627
+ const { initCompanion } = await import("./companion-XKMNRIJB.js");
11097
11628
  initCompanion();
11098
11629
  return { app, server, wss, shutdown };
11099
11630
  }
@@ -11112,7 +11643,7 @@ var PullEnvError = class extends Error {
11112
11643
  };
11113
11644
  async function promptPassword() {
11114
11645
  process.stderr.write("Master password: ");
11115
- return new Promise((resolve4) => {
11646
+ return new Promise((resolve5) => {
11116
11647
  const stdin = process.stdin;
11117
11648
  const wasRaw = stdin.isRaw;
11118
11649
  if (stdin.isTTY) stdin.setRawMode(true);
@@ -11125,7 +11656,7 @@ async function promptPassword() {
11125
11656
  stdin.pause();
11126
11657
  stdin.removeListener("data", onData);
11127
11658
  process.stderr.write("\n");
11128
- resolve4(input);
11659
+ resolve5(input);
11129
11660
  } else if (ch === "") {
11130
11661
  process.exit(1);
11131
11662
  } else if (ch === "\x7F" || ch === "\b") {
@@ -11223,8 +11754,8 @@ function onTrayServicesChanged(listener) {
11223
11754
  }
11224
11755
 
11225
11756
  // ../server/src/services/tray-icon.ts
11226
- import { mkdir as mkdir12, writeFile as writeFile10, rename as rename6 } from "fs/promises";
11227
- import { join as join18 } from "path";
11757
+ import { mkdir as mkdir13, writeFile as writeFile10, rename as rename6 } from "fs/promises";
11758
+ import { join as join19 } from "path";
11228
11759
 
11229
11760
  // ../server/src/services/png-to-ico.ts
11230
11761
  function isPng(bytes) {
@@ -11384,7 +11915,7 @@ var FETCH_TIMEOUT_MS = 3e3;
11384
11915
  var CACHE_TTL_MS = 10 * 6e4;
11385
11916
  var cache2 = /* @__PURE__ */ new Map();
11386
11917
  function trayIconDir() {
11387
- return join18(machineRoot(), "tray-icons");
11918
+ return join19(machineRoot(), "tray-icons");
11388
11919
  }
11389
11920
  async function resolveTrayIcon(platform = process.platform) {
11390
11921
  const orgId = getCurrentProject()?.orgId;
@@ -11412,8 +11943,8 @@ async function renderThemedIcon(platform) {
11412
11943
  }
11413
11944
  async function writeTrayIcon(stem, png, platform) {
11414
11945
  const dir = trayIconDir();
11415
- await mkdir12(dir, { recursive: true });
11416
- const path = join18(dir, stem);
11946
+ await mkdir13(dir, { recursive: true });
11947
+ const path = join19(dir, stem);
11417
11948
  if (platform === "win32") {
11418
11949
  const ico = pngToIco(png);
11419
11950
  return ico ? writeAtomic(`${path}.ico`, ico) : null;
@@ -11455,7 +11986,7 @@ if (process.env["RUNEYA_DATA_DIR"]) {
11455
11986
  console.log(`[server] Working directory: ${resolved} (from RUNEYA_DATA_DIR)`);
11456
11987
  }
11457
11988
  function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500) {
11458
- return new Promise((resolve4, reject) => {
11989
+ return new Promise((resolve5, reject) => {
11459
11990
  let attempts = 0;
11460
11991
  const wssErrorHandler = () => {
11461
11992
  };
@@ -11476,7 +12007,7 @@ function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500)
11476
12007
  server.listen(port, host, () => {
11477
12008
  server.removeListener("error", onError);
11478
12009
  wss?.removeListener("error", wssErrorHandler);
11479
- resolve4();
12010
+ resolve5();
11480
12011
  });
11481
12012
  };
11482
12013
  tryListen();
@@ -11489,6 +12020,7 @@ async function main() {
11489
12020
  console.log("Hello World 2");
11490
12021
  const { server, wss, shutdown } = await createLocalServer();
11491
12022
  await listenWithRetry(server, env.PORT, env.HOST, wss);
12023
+ await registerInstance();
11492
12024
  console.log(`[server] Local server listening on ${env.HOST}:${env.PORT}`);
11493
12025
  console.log(`[server] tRPC HTTP: http://${env.HOST}:${env.PORT}/api/trpc`);
11494
12026
  console.log(`[server] tRPC WS: ws://${env.HOST}:${env.PORT}`);
@@ -11548,4 +12080,4 @@ export {
11548
12080
  stopProcess,
11549
12081
  userPrefix
11550
12082
  };
11551
- //# sourceMappingURL=src-IODFUZ7A.js.map
12083
+ //# sourceMappingURL=src-KWO3CKZU.js.map