@runeya/runeya 2.0.144 → 2.0.146

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 (148) hide show
  1. package/agent/index.js +33 -22
  2. package/agent/index.js.map +1 -1
  3. package/agent-manager-R7BPQO6D.js +11 -0
  4. package/chat-LPML22J5.js +24 -0
  5. package/{chunk-7L6AQUBR.js → chunk-4LJQIQMN.js} +6 -2
  6. package/{chunk-7L6AQUBR.js.map → chunk-4LJQIQMN.js.map} +1 -1
  7. package/{chunk-7HIPFSOA.js → chunk-57P45FNY.js} +11 -3
  8. package/chunk-57P45FNY.js.map +1 -0
  9. package/chunk-6IBPK6UE.js +1063 -0
  10. package/chunk-6IBPK6UE.js.map +1 -0
  11. package/{chunk-GXE5TLLJ.js → chunk-KV4GJKXU.js} +17 -17
  12. package/{chunk-XAFOP677.js → chunk-MZSDTA4A.js} +480 -233
  13. package/chunk-MZSDTA4A.js.map +1 -0
  14. package/{chunk-UR3HL373.js → chunk-NPMSBYXC.js} +8 -8
  15. package/{chunk-P73PTVII.js → chunk-SEPAURTQ.js} +369 -1282
  16. package/chunk-SEPAURTQ.js.map +1 -0
  17. package/{chunk-NSS47H26.js → chunk-UD42LACH.js} +348 -78
  18. package/chunk-UD42LACH.js.map +1 -0
  19. package/{chunk-IXO5KIO5.js → chunk-VPSQJOSN.js} +23 -2
  20. package/{chunk-IXO5KIO5.js.map → chunk-VPSQJOSN.js.map} +1 -1
  21. package/{companion-2LHKU577.js → companion-W3UMZWGH.js} +11 -7
  22. package/{desktop-installer-page-6JDSXK2E.js → desktop-installer-page-I5FHFDES.js} +11 -8
  23. package/desktop-installer-page-I5FHFDES.js.map +1 -0
  24. package/{desktop-launcher-YPNRQMIS.js → desktop-launcher-GD4QPM66.js} +3 -3
  25. package/external-NMKQPROK.js +387 -0
  26. package/external-NMKQPROK.js.map +1 -0
  27. package/index.js +34 -12
  28. package/index.js.map +1 -1
  29. package/{install-gate-2NNH7RM2.js → install-gate-PVMGJDWE.js} +2 -2
  30. package/mcp-server/external.d.ts +44 -0
  31. package/mcp-server/external.js +2679 -0
  32. package/mcp-server/external.js.map +1 -0
  33. package/mcp-server/index.js +142 -40
  34. package/mcp-server/index.js.map +1 -1
  35. package/mcp-server/launch.d.ts +19 -1
  36. package/mcp-server/launch.js +20 -0
  37. package/mcp-server/launch.js.map +1 -1
  38. package/package.json +1 -1
  39. package/settings-manager-XPLBO35P.js +10 -0
  40. package/src-DCCEUWE6.js +385 -0
  41. package/{src-5TEXF3SU.js → src-LEDGW5KP.js} +1029 -536
  42. package/src-LEDGW5KP.js.map +1 -0
  43. package/{vault-resolver-VK6W5J6W.js → vault-resolver-E7KPKA5C.js} +3 -2
  44. package/vault-resolver-E7KPKA5C.js.map +1 -0
  45. package/web/assets/{AgentScalarChatInterface.vue-B-SjOsdj.js → AgentScalarChatInterface.vue-BYszptaB.js} +1 -1
  46. package/web/assets/{AgentTerminalPage-JzC8DUgu.js → AgentTerminalPage-C3TJ1NL9.js} +1 -1
  47. package/web/assets/{AiChatModals-D1928AxG.js → AiChatModals-D3scRE3S.js} +1 -1
  48. package/web/assets/AppLayout-BMQUjPKe.js +1 -0
  49. package/web/assets/{AppLayout-8sBPr3od.css → AppLayout-tsw5Ow0w.css} +1 -1
  50. package/web/assets/{AuditPage-BZIJ3_Kr.js → AuditPage-CKcExBws.js} +1 -1
  51. package/web/assets/{CloudAccountPage-DBv470sT.js → CloudAccountPage-B6oPg0BZ.js} +1 -1
  52. package/web/assets/{CloudMessagingPage-qCI8-UDL.js → CloudMessagingPage-C5fwVe-F.js} +1 -1
  53. package/web/assets/DashboardPage-9RV7sx3e.js +1 -0
  54. package/web/assets/{DashboardPage-D9-PpC3F.css → DashboardPage-FvNuC_e4.css} +1 -1
  55. package/web/assets/{DirectoryPickerDialog-B5DvDfQP.js → DirectoryPickerDialog-C06BXyfJ.js} +1 -1
  56. package/web/assets/{EnvironmentForm-Bw8Djnqm.js → EnvironmentForm-CAvDoira.js} +1 -1
  57. package/web/assets/{EnvironmentFormPage-B59uiYtp.js → EnvironmentFormPage-BmAxhN-m.js} +1 -1
  58. package/web/assets/{EnvironmentListPage-W9zLDZAg.js → EnvironmentListPage-B8uATPBI.js} +1 -1
  59. package/web/assets/KanbanBoardPage-CQsOkRUf.js +4 -0
  60. package/web/assets/KanbanBoardPage-eHmsrFGA.css +1 -0
  61. package/web/assets/{KanbanListPage-Bn9k8Rpc.js → KanbanListPage-CWZqkjD6.js} +1 -1
  62. package/web/assets/{LaunchProjectPage-Dc7v1UjQ.js → LaunchProjectPage-saSmC7Pr.js} +1 -1
  63. package/web/assets/LaunchServicesPage-BkKyLMEz.js +1 -0
  64. package/web/assets/LaunchServicesPage-DoLDPqQq.css +1 -0
  65. package/web/assets/{LaunchShell-LonTJpb3.js → LaunchShell-DUrSunB4.js} +1 -1
  66. package/web/assets/{LogViewer-DkuqWYkQ.js → LogViewer-CZBYkH9J.js} +1 -1
  67. package/web/assets/{OrgKeyPrompt-By1VjlSN.js → OrgKeyPrompt-DzQ7pwbs.js} +1 -1
  68. package/web/assets/{ProcessControls-C0skzKGL.js → ProcessControls-BEeoNlNV.js} +1 -1
  69. package/web/assets/{ProcessDetailPage-BvPX6z_X.js → ProcessDetailPage-CQgu5uKm.js} +1 -1
  70. package/web/assets/{ProjectCreateDialog-C4o9u15L.js → ProjectCreateDialog-BolrhRD-.js} +1 -1
  71. package/web/assets/{ProjectLocationGate-BJ1CSQt0.js → ProjectLocationGate-DhMVdIjN.js} +1 -1
  72. package/web/assets/{ProjectPublishDialog-Cp-zXdGm.js → ProjectPublishDialog-DsZNWrfz.js} +1 -1
  73. package/web/assets/{RandomJobTestPage-DF4a1U13.js → RandomJobTestPage-BUS2Ty6-.js} +1 -1
  74. package/web/assets/ScenarioEditorPage-MIAGh2us.js +14 -0
  75. package/web/assets/{SendToChatDialog-Dg2SHkVD.js → SendToChatDialog-BxWsyv29.js} +1 -1
  76. package/web/assets/{ServiceControlsBar-q2VHWaxK.js → ServiceControlsBar-DqDPAZt7.js} +1 -1
  77. package/web/assets/ServiceDetailPage-hkSAzZ-l.js +2 -0
  78. package/web/assets/{ServiceFormPage-tT_n7WUy.js → ServiceFormPage-DXqhqXg8.js} +1 -1
  79. package/web/assets/SettingsPage-4pKUgKPm.css +1 -0
  80. package/web/assets/SettingsPage-DS2YhMIM.js +2086 -0
  81. package/web/assets/{UnlockPage-BKAdMJkb.js → UnlockPage-s8SgVcKk.js} +1 -1
  82. package/web/assets/{VaultMigrationPage-D9KUTTss.js → VaultMigrationPage-s0b44MDM.js} +1 -1
  83. package/web/assets/{VersionBadge-moKP5bAv.js → VersionBadge-BmArk24p.js} +1 -1
  84. package/web/assets/{WikiPage-hLUndDxo.js → WikiPage-DADu8WUA.js} +1 -1
  85. package/web/assets/{WikiView-rrjXRR72.js → WikiView-DtGwIt_Q.js} +1 -1
  86. package/web/assets/{add-scalar-classes-CBfRr8B2.js → add-scalar-classes-dWEWPlFD.js} +2 -2
  87. package/web/assets/{agent.store-B3dVemmT.js → agent.store-CKLAkF9a.js} +1 -1
  88. package/web/assets/app-mode-BVpTuh0i.js +1 -0
  89. package/web/assets/auth-Cg9BWTfK.js +2 -0
  90. package/web/assets/{cloud-chat.store-Wg1poFem.js → cloud-chat.store-b-BHGz7I.js} +1 -1
  91. package/web/assets/{cloud-client-DQlUaoAw.js → cloud-client-D_zCiW0s.js} +2 -2
  92. package/web/assets/{cloud-session-DRUqcSGP.js → cloud-session-DOaDpZTy.js} +2 -2
  93. package/web/assets/dist-N4nrnbnA.js +1 -0
  94. package/web/assets/{editor.store-SoH48ux2.js → editor.store-DCAZSeVG.js} +1 -1
  95. package/web/assets/i18n-D0afQEGs.js +1 -0
  96. package/web/assets/{index-D_x2s8Ut.js → index-D8VRbyoP.js} +101 -101
  97. package/web/assets/index-DsezNbSM.css +1 -0
  98. package/web/assets/{modal-CpsVK_dA.js → modal-qYgTMkYb.js} +1 -1
  99. package/web/assets/{project-launch.store-C3fZeRGb.js → project-launch.store-y7JBMNiE.js} +1 -1
  100. package/web/assets/{project.store-BEk4fciH.js → project.store-DGJr0Qr9.js} +1 -1
  101. package/web/assets/{scenario.store-D-JrRiVO.js → scenario.store-YWHL2_wu.js} +3 -3
  102. package/web/assets/{service.store-B5uYZtyM.js → service.store-5jWsJxPx.js} +1 -1
  103. package/web/assets/{settings-scope-Blnwj3rR.js → settings-scope-Bv7QGPrt.js} +1 -1
  104. package/web/assets/{settings.store-Bo3FYs1o.js → settings.store-bEsB7oKb.js} +1 -1
  105. package/web/assets/{src-CAwAaB5j.js → src-C-D_y7ou.js} +1 -1
  106. package/web/assets/{src-DValZNJU.js → src-Dnwk6NpV.js} +5 -5
  107. package/web/assets/src-g1DRo8xZ.css +1 -0
  108. package/web/assets/{trpc-DbsE5GvQ.js → trpc-Drabzwpd.js} +3 -3
  109. package/web/assets/{useCloudOrg-DygUBG1a.js → useCloudOrg-BAQEtFPl.js} +1 -1
  110. package/web/assets/{useEnvVarAutocomplete-DmYNOXgd.js → useEnvVarAutocomplete-BqGTODUv.js} +1 -1
  111. package/web/assets/{useProjectCatalog-D2mHB7dg.js → useProjectCatalog-yocjIKt6.js} +1 -1
  112. package/web/assets/{useProjectCloudOrg-avTCVold.js → useProjectCloudOrg-CEN9-_OA.js} +1 -1
  113. package/web/assets/{useServiceUrlHost-Q3fzGox3.js → useServiceUrlHost-BuoPEXZX.js} +1 -1
  114. package/web/assets/{vue-draggable-plus-WgrMXXmE.js → vue-draggable-plus-DX4rTBiu.js} +1 -1
  115. package/web/index.html +19 -18
  116. package/agent-manager-M4NA5YFD.js +0 -11
  117. package/chat-X3X527EX.js +0 -23
  118. package/chunk-7HIPFSOA.js.map +0 -1
  119. package/chunk-NSS47H26.js.map +0 -1
  120. package/chunk-P73PTVII.js.map +0 -1
  121. package/chunk-XAFOP677.js.map +0 -1
  122. package/desktop-installer-page-6JDSXK2E.js.map +0 -1
  123. package/settings-manager-32SVMAFN.js +0 -10
  124. package/src-5TEXF3SU.js.map +0 -1
  125. package/web/assets/AppLayout-CVQIBYqg.js +0 -1
  126. package/web/assets/DashboardPage-D0UZM0mr.js +0 -1
  127. package/web/assets/KanbanBoardPage-DRACMk_D.css +0 -1
  128. package/web/assets/KanbanBoardPage-DwPqoJDJ.js +0 -4
  129. package/web/assets/LaunchServicesPage-CXUS9O3y.js +0 -1
  130. package/web/assets/LaunchServicesPage-DJDIN8HU.css +0 -1
  131. package/web/assets/ScenarioEditorPage-Dl-gEkJI.js +0 -14
  132. package/web/assets/ServiceDetailPage-CJ8WaFod.js +0 -2
  133. package/web/assets/SettingsPage-CWakkv0V.js +0 -2086
  134. package/web/assets/SettingsPage-aGSxZw1n.css +0 -1
  135. package/web/assets/auth-BCrvnP1U.js +0 -2
  136. package/web/assets/dist-DoChtT9t.js +0 -1
  137. package/web/assets/i18n-BszFg0ko.js +0 -1
  138. package/web/assets/index-C_JV7fYy.css +0 -1
  139. package/web/assets/src-DuBXN1hu.css +0 -1
  140. /package/{agent-manager-M4NA5YFD.js.map → agent-manager-R7BPQO6D.js.map} +0 -0
  141. /package/{chat-X3X527EX.js.map → chat-LPML22J5.js.map} +0 -0
  142. /package/{chunk-GXE5TLLJ.js.map → chunk-KV4GJKXU.js.map} +0 -0
  143. /package/{chunk-UR3HL373.js.map → chunk-NPMSBYXC.js.map} +0 -0
  144. /package/{companion-2LHKU577.js.map → companion-W3UMZWGH.js.map} +0 -0
  145. /package/{desktop-launcher-YPNRQMIS.js.map → desktop-launcher-GD4QPM66.js.map} +0 -0
  146. /package/{install-gate-2NNH7RM2.js.map → install-gate-PVMGJDWE.js.map} +0 -0
  147. /package/{settings-manager-32SVMAFN.js.map → settings-manager-XPLBO35P.js.map} +0 -0
  148. /package/{vault-resolver-VK6W5J6W.js.map → src-DCCEUWE6.js.map} +0 -0
@@ -19,6 +19,7 @@ import {
19
19
  applyPersistedLanMode,
20
20
  attachServiceToProject,
21
21
  auditService,
22
+ buildKanbanCardPrompt,
22
23
  buildServerBuiltins,
23
24
  canEnvAction,
24
25
  chatRouter,
@@ -49,6 +50,8 @@ import {
49
50
  readCloudIdentity,
50
51
  registerHttpServer,
51
52
  requireEnvAction,
53
+ requireHuman,
54
+ requireLoopback,
52
55
  requireLoopbackOrToken,
53
56
  requireServiceAction,
54
57
  resolveEffectiveAgentId,
@@ -62,13 +65,16 @@ import {
62
65
  soleAccountScope,
63
66
  startCliSession,
64
67
  vaultOfProject
65
- } from "./chunk-P73PTVII.js";
68
+ } from "./chunk-SEPAURTQ.js";
69
+ import "./chunk-6IBPK6UE.js";
66
70
  import {
67
71
  settingsManager
68
- } from "./chunk-UR3HL373.js";
72
+ } from "./chunk-NPMSBYXC.js";
69
73
  import {
74
+ CONVERSATIONS_DIRNAME,
70
75
  LocalProjectRefusedError,
71
76
  activeSessionRegistry,
77
+ companionCreateDefaults,
72
78
  companionDefaultPrompt,
73
79
  companionSettings,
74
80
  conversationStore,
@@ -81,13 +87,14 @@ import {
81
87
  resolveAgentCwd,
82
88
  resolveConversationCwd,
83
89
  resolveWorkspaceCwd,
90
+ setMcpClientCheck,
84
91
  signJwt,
85
92
  startClaudeTranscriptWatcher,
86
93
  startCodexTranscriptWatcher,
87
94
  stopClaudeTranscriptWatcher,
88
95
  stopCodexTranscriptWatcher,
89
96
  verifyJwt
90
- } from "./chunk-NSS47H26.js";
97
+ } from "./chunk-UD42LACH.js";
91
98
  import {
92
99
  agentManager,
93
100
  agentSpawner,
@@ -126,14 +133,48 @@ import {
126
133
  stableEncryptValue,
127
134
  traefikManager,
128
135
  unregisterInstance
129
- } from "./chunk-GXE5TLLJ.js";
136
+ } from "./chunk-KV4GJKXU.js";
137
+ import {
138
+ decryptValueWith,
139
+ encryptValueWith,
140
+ isCliAvailable,
141
+ resolveCliInvocation,
142
+ whichCommand
143
+ } from "./chunk-OZBSOSAS.js";
130
144
  import {
131
145
  atomicWriteSecure,
132
146
  renameWithRetry,
133
147
  resolveConversationRunDir,
134
148
  sweepStaleTempFiles
135
- } from "./chunk-7L6AQUBR.js";
149
+ } from "./chunk-4LJQIQMN.js";
136
150
  import "./chunk-MD3VW4HQ.js";
151
+ import {
152
+ allVaultRoots,
153
+ backfillOrgCloud,
154
+ env,
155
+ forgetOrgLogo,
156
+ keyRoot,
157
+ keyedFilePath,
158
+ legacyMachineFilePath,
159
+ legacyVaultRoot,
160
+ listCloudVaults,
161
+ listOrgVaults,
162
+ localVaultCollidesWithMachineRoot,
163
+ machineFilePath,
164
+ machineRoot,
165
+ orgVaultRoot,
166
+ projectVaultRoot,
167
+ readOrgAccounts,
168
+ readOrgClouds,
169
+ readOrgLogo,
170
+ readOrgLogos,
171
+ readOrgNames,
172
+ rememberOrgAccount,
173
+ rememberOrgLogo,
174
+ rememberOrgName,
175
+ stampOrgCloud,
176
+ vaultFor
177
+ } from "./chunk-VPSQJOSN.js";
137
178
  import {
138
179
  AGENT_LONG_FETCH_TIMEOUT,
139
180
  AGENT_STOP_FETCH_TIMEOUT,
@@ -146,6 +187,7 @@ import {
146
187
  EDITOR_BY_KEY,
147
188
  JSLogParser,
148
189
  JSLogParserSchema,
190
+ LAUNCHER_PROJECT_ID_PATTERN,
149
191
  LOG_BUFFER_SIZE,
150
192
  MissingVariableReasonSchema,
151
193
  NotificationPreferencesSchema,
@@ -170,45 +212,13 @@ import {
170
212
  serviceSlug,
171
213
  suggestBuiltinReferences,
172
214
  writeEntity
173
- } from "./chunk-XAFOP677.js";
174
- import {
175
- allVaultRoots,
176
- backfillOrgCloud,
177
- env,
178
- forgetOrgLogo,
179
- keyRoot,
180
- legacyMachineFilePath,
181
- legacyVaultRoot,
182
- listCloudVaults,
183
- listOrgVaults,
184
- localVaultCollidesWithMachineRoot,
185
- machineFilePath,
186
- machineRoot,
187
- orgVaultRoot,
188
- projectVaultRoot,
189
- readOrgAccounts,
190
- readOrgClouds,
191
- readOrgLogo,
192
- readOrgLogos,
193
- readOrgNames,
194
- rememberOrgAccount,
195
- rememberOrgLogo,
196
- rememberOrgName,
197
- stampOrgCloud,
198
- vaultFor
199
- } from "./chunk-IXO5KIO5.js";
200
- import {
201
- decryptValueWith,
202
- encryptValueWith,
203
- resolveCliInvocation,
204
- whichCommand
205
- } from "./chunk-OZBSOSAS.js";
215
+ } from "./chunk-MZSDTA4A.js";
206
216
 
207
217
  // ../server/src/create-server.ts
208
218
  import express from "express";
209
219
  import cors from "cors";
210
220
  import { createServer } from "http";
211
- import { resolve as resolve3 } from "path";
221
+ import { join as join18, resolve as resolve4 } from "path";
212
222
  import { WebSocketServer } from "ws";
213
223
  import { createExpressMiddleware } from "@trpc/server/adapters/express";
214
224
  import { applyWSSHandler } from "@trpc/server/adapters/ws";
@@ -223,7 +233,16 @@ import { z } from "zod";
223
233
  import { execFile } from "child_process";
224
234
  import { promisify } from "util";
225
235
  import { existsSync, mkdtempSync, readFileSync, rmdirSync } from "fs";
226
- import { dirname, isAbsolute, join, relative } from "path";
236
+ import { dirname, join } from "path";
237
+
238
+ // ../server/src/utils/is-inside.ts
239
+ import { isAbsolute, relative } from "path";
240
+ function isInside(dir, path) {
241
+ const rel = relative(dir, path);
242
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
243
+ }
244
+
245
+ // ../server/src/services/npm-cli.ts
227
246
  var execFileAsync = promisify(execFile);
228
247
  var PACKAGE_NAME = "@runeya/runeya";
229
248
  var INSTALL_TIMEOUT_MS = 10 * 6e4;
@@ -329,10 +348,6 @@ function installPrefix(globalPrefix, platform = process.platform) {
329
348
  const modules = platform === "win32" ? join(globalPrefix, "node_modules") : join(globalPrefix, "lib", "node_modules");
330
349
  return isWritable(modules) ? globalPrefix : userPrefix();
331
350
  }
332
- function isInside(dir, path) {
333
- const rel = relative(dir, path);
334
- return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
335
- }
336
351
  function updatePrefix(globalPrefix, runningEntry) {
337
352
  if (isInside(userPrefix(), runningEntry)) return userPrefix();
338
353
  if (!globalPrefix) return null;
@@ -639,6 +654,23 @@ function onCurrentProjectChanged(listener) {
639
654
  return () => listeners.delete(listener);
640
655
  }
641
656
 
657
+ // ../server/src/services/project-containing.ts
658
+ import { resolve } from "path";
659
+ function projectContaining(paths, path) {
660
+ const target = resolve(path);
661
+ let best = null;
662
+ for (const [key, dir] of Object.entries(paths)) {
663
+ const root = resolve(dir);
664
+ if (root !== target && !isInside(root, target)) continue;
665
+ if (!best || root.length > best.depth) best = { key, depth: root.length };
666
+ }
667
+ if (!best) return null;
668
+ const sep2 = best.key.indexOf(":");
669
+ if (sep2 === -1) return { id: best.key };
670
+ const owner = best.key.slice(0, sep2);
671
+ return { id: best.key.slice(sep2 + 1), orgId: owner === "local" ? null : owner };
672
+ }
673
+
642
674
  // ../server/src/services/org-keys.ts
643
675
  import { mkdir } from "fs/promises";
644
676
 
@@ -947,6 +979,13 @@ async function assertReadable(orgId) {
947
979
  throw err;
948
980
  }
949
981
  }
982
+ async function localProjectPaths() {
983
+ const paths = await projectPathStore.all();
984
+ for (const project of await projectStore.list()) {
985
+ if (!project.orgId) paths[`local:${project.id}`] = dirname2(vaultFor(project).root);
986
+ }
987
+ return paths;
988
+ }
950
989
  var projectRouter = router({
951
990
  list: protectedProcedure.query(async () => {
952
991
  return projectStore.list();
@@ -1228,12 +1267,16 @@ var projectRouter = router({
1228
1267
  * Le dossier de chaque projet sur cette machine, par clé `org:projet`
1229
1268
  * (`local:<id>` pour un projet local, dont le dossier contient son `.runeya`).
1230
1269
  */
1231
- localPaths: protectedProcedure.query(async () => {
1232
- const paths = await projectPathStore.all();
1233
- for (const project of await projectStore.list()) {
1234
- if (!project.orgId) paths[`local:${project.id}`] = dirname2(vaultFor(project).root);
1235
- }
1236
- return paths;
1270
+ localPaths: protectedProcedure.query(() => localProjectPaths()),
1271
+ /**
1272
+ * Le projet dont le dossier contient ce chemin, ou `null`. Sert d'ancrage à
1273
+ * un outil IA lancé hors de Runeya (`runeya mcp`), qui sait seulement dans
1274
+ * quel dossier il tourne.
1275
+ */
1276
+ containing: protectedProcedure.input(z5.object({ path: z5.string().min(1).max(4096) })).query(async ({ input }) => {
1277
+ const ref = projectContaining(await localProjectPaths(), input.path);
1278
+ const project = ref ? await projectStore.get(ref).catch(() => null) : null;
1279
+ return project ? { projectId: project.id, orgId: project.orgId ?? null, name: project.name } : null;
1237
1280
  }),
1238
1281
  getLocalPath: protectedProcedure.input(z5.object({
1239
1282
  id: z5.string().min(1).max(128),
@@ -1331,8 +1374,8 @@ var projectRouter = router({
1331
1374
  touched.clear();
1332
1375
  yield { collections };
1333
1376
  } else {
1334
- await new Promise((resolve4) => {
1335
- wake = resolve4;
1377
+ await new Promise((resolve5) => {
1378
+ wake = resolve5;
1336
1379
  });
1337
1380
  }
1338
1381
  }
@@ -1480,13 +1523,13 @@ var agentRouter = router({
1480
1523
  yield { agentId: agent.config.id, status: agent.status };
1481
1524
  }
1482
1525
  const queue = [];
1483
- let resolve4 = null;
1526
+ let resolve5 = null;
1484
1527
  const handler = (event) => {
1485
1528
  if (queue.length >= SUBSCRIPTION_QUEUE_MAX) queue.shift();
1486
1529
  queue.push(event);
1487
- if (resolve4) {
1488
- resolve4();
1489
- resolve4 = null;
1530
+ if (resolve5) {
1531
+ resolve5();
1532
+ resolve5 = null;
1490
1533
  }
1491
1534
  };
1492
1535
  agentManager.on("status", handler);
@@ -1496,7 +1539,7 @@ var agentRouter = router({
1496
1539
  yield queue.shift();
1497
1540
  } else {
1498
1541
  await new Promise((r) => {
1499
- resolve4 = r;
1542
+ resolve5 = r;
1500
1543
  });
1501
1544
  }
1502
1545
  }
@@ -1615,14 +1658,14 @@ async function listAllProcesses() {
1615
1658
  var SUBSCRIPTION_QUEUE_MAX2 = LOG_BUFFER_SIZE;
1616
1659
  async function* forwardEvents(eventName, filter) {
1617
1660
  const queue = [];
1618
- let resolve4 = null;
1661
+ let resolve5 = null;
1619
1662
  const handler = (data) => {
1620
1663
  if (filter && !filter(data)) return;
1621
1664
  if (queue.length >= SUBSCRIPTION_QUEUE_MAX2) queue.shift();
1622
1665
  queue.push(data);
1623
- if (resolve4) {
1624
- resolve4();
1625
- resolve4 = null;
1666
+ if (resolve5) {
1667
+ resolve5();
1668
+ resolve5 = null;
1626
1669
  }
1627
1670
  };
1628
1671
  agentManager.on(eventName, handler);
@@ -1632,7 +1675,7 @@ async function* forwardEvents(eventName, filter) {
1632
1675
  yield queue.shift();
1633
1676
  } else {
1634
1677
  await new Promise((r) => {
1635
- resolve4 = r;
1678
+ resolve5 = r;
1636
1679
  });
1637
1680
  }
1638
1681
  }
@@ -1642,7 +1685,7 @@ async function* forwardEvents(eventName, filter) {
1642
1685
  }
1643
1686
  async function* forwardLogBatches(agentId, processId) {
1644
1687
  const queue = [];
1645
- let resolve4 = null;
1688
+ let resolve5 = null;
1646
1689
  const handler = (data) => {
1647
1690
  if (data.agentId !== agentId) return;
1648
1691
  const batch = data.batch;
@@ -1651,9 +1694,9 @@ async function* forwardLogBatches(agentId, processId) {
1651
1694
  if (filtered.length === 0) return;
1652
1695
  if (queue.length >= SUBSCRIPTION_QUEUE_MAX2) queue.shift();
1653
1696
  queue.push(filtered);
1654
- if (resolve4) {
1655
- resolve4();
1656
- resolve4 = null;
1697
+ if (resolve5) {
1698
+ resolve5();
1699
+ resolve5 = null;
1657
1700
  }
1658
1701
  };
1659
1702
  agentManager.on("process:log", handler);
@@ -1663,7 +1706,7 @@ async function* forwardLogBatches(agentId, processId) {
1663
1706
  yield queue.shift();
1664
1707
  } else {
1665
1708
  await new Promise((r) => {
1666
- resolve4 = r;
1709
+ resolve5 = r;
1667
1710
  });
1668
1711
  }
1669
1712
  }
@@ -2553,6 +2596,10 @@ var ScopeShape = {
2553
2596
  async function scopeOf(input) {
2554
2597
  return input?.orgId ?? await vaultOfProject(input?.projectId);
2555
2598
  }
2599
+ function keepOrReplace(key, current2, next) {
2600
+ const value = next === void 0 ? current2 : next || void 0;
2601
+ return value ? { [key]: value } : {};
2602
+ }
2556
2603
  var settingsRouter = router({
2557
2604
  /**
2558
2605
  * Get all settings
@@ -2681,8 +2728,8 @@ var settingsRouter = router({
2681
2728
  pending = false;
2682
2729
  yield listWithOrigin(await logParserStore.list(vault), vault);
2683
2730
  } else {
2684
- await new Promise((resolve4) => {
2685
- wake = resolve4;
2731
+ await new Promise((resolve5) => {
2732
+ wake = resolve5;
2686
2733
  });
2687
2734
  }
2688
2735
  }
@@ -2772,6 +2819,10 @@ var settingsRouter = router({
2772
2819
  return {
2773
2820
  enabledByDefault: c.enabledByDefault ?? false,
2774
2821
  prompt: c.prompt ?? "",
2822
+ /** Chaîne vide = pas de runner imposé : celui de la conversation. */
2823
+ runner: c.runner ?? "",
2824
+ model: c.model ?? "",
2825
+ effort: c.effort ?? "",
2775
2826
  /**
2776
2827
  * Conservé dans la réponse pour ne pas casser un client déjà construit,
2777
2828
  * mais l'écran n'en fait plus un texte à modifier : le réglage ajoute
@@ -2784,7 +2835,11 @@ var settingsRouter = router({
2784
2835
  enabledByDefault: z11.boolean().optional(),
2785
2836
  // Rejeté au-delà, jamais tronqué : un prompt coupé produirait des
2786
2837
  // relectures incohérentes sans rien signaler.
2787
- prompt: z11.string().max(2e4).optional()
2838
+ prompt: z11.string().max(2e4).optional(),
2839
+ /** Chaîne vide = retirer le défaut. */
2840
+ runner: z11.enum(["claude-code", "codex", ""]).optional(),
2841
+ model: z11.string().max(200).optional(),
2842
+ effort: z11.enum(["low", "normal", "high", ""]).optional()
2788
2843
  })).mutation(async ({ input }) => {
2789
2844
  const settings = await settingsManager.getSettings();
2790
2845
  const current2 = settings.companion ?? { enabledByDefault: false };
@@ -2795,7 +2850,10 @@ var settingsRouter = router({
2795
2850
  enabledByDefault: input.enabledByDefault ?? current2.enabledByDefault ?? false,
2796
2851
  // Vide = « celles de Runeya » : on efface le réglage plutôt que d'y
2797
2852
  // ranger une chaîne vide qui signifierait « aucune consigne ».
2798
- ...input.prompt === void 0 ? current2.prompt ? { prompt: current2.prompt } : {} : prompt ? { prompt } : {}
2853
+ ...input.prompt === void 0 ? current2.prompt ? { prompt: current2.prompt } : {} : prompt ? { prompt } : {},
2854
+ ...keepOrReplace("runner", current2.runner, input.runner),
2855
+ ...keepOrReplace("model", current2.model, input.model?.trim()),
2856
+ ...keepOrReplace("effort", current2.effort, input.effort)
2799
2857
  }
2800
2858
  });
2801
2859
  return { ok: true };
@@ -3028,7 +3086,7 @@ var settingsRouter = router({
3028
3086
  }
3029
3087
  } catch {
3030
3088
  }
3031
- await new Promise((resolve4) => setTimeout(resolve4, 500));
3089
+ await new Promise((resolve5) => setTimeout(resolve5, 500));
3032
3090
  }
3033
3091
  }),
3034
3092
  /**
@@ -3215,7 +3273,7 @@ import { z as z13 } from "zod";
3215
3273
  import { TRPCError as TRPCError9 } from "@trpc/server";
3216
3274
  import { execFile as execFile3, spawn as spawn2 } from "child_process";
3217
3275
  import { stat as stat2 } from "fs/promises";
3218
- import { resolve, join as join4 } from "path";
3276
+ import { resolve as resolve2, join as join4 } from "path";
3219
3277
  import { homedir as homedir2 } from "os";
3220
3278
 
3221
3279
  // ../server/src/services/user-config-manager.ts
@@ -3310,9 +3368,9 @@ async function getServiceCwd(serviceId) {
3310
3368
  return resolveCwd(await resolveWorkspaceCwd({ serviceId }));
3311
3369
  }
3312
3370
  async function isCommandAvailable(command) {
3313
- return new Promise((resolve4) => {
3371
+ return new Promise((resolve5) => {
3314
3372
  execFile3(whichCommand(), [command], { timeout: 5e3, windowsHide: true }, (err) => {
3315
- resolve4(!err);
3373
+ resolve5(!err);
3316
3374
  });
3317
3375
  });
3318
3376
  }
@@ -3383,7 +3441,7 @@ var editorRouter = router({
3383
3441
  throw new TRPCError9({ code: "BAD_REQUEST", message: `Unknown editor: ${input.editorKey}` });
3384
3442
  }
3385
3443
  const expanded = input.path.replace(/^~(\/|$)/, `${process.env["HOME"] ?? "/tmp"}$1`).replace(/\$\{(\w+)\}|\$(\w+)/g, (_, a, b) => process.env[a || b] ?? "");
3386
- const resolvedPath = resolve(expanded || ".");
3444
+ const resolvedPath = resolve2(expanded || ".");
3387
3445
  try {
3388
3446
  await stat2(resolvedPath);
3389
3447
  } catch (err) {
@@ -3690,7 +3748,7 @@ Rules:
3690
3748
  onStatusChange: protectedProcedure.input(z15.object({ ...gitTargetShape })).subscription(async function* ({ input }) {
3691
3749
  const { agentId, cwd } = await resolveGitContext(input);
3692
3750
  const queue = [];
3693
- let resolve4 = null;
3751
+ let resolve5 = null;
3694
3752
  let done = false;
3695
3753
  let unsubscribe;
3696
3754
  try {
@@ -3700,16 +3758,16 @@ Rules:
3700
3758
  { cwd },
3701
3759
  (data) => {
3702
3760
  queue.push(data);
3703
- if (resolve4) {
3704
- resolve4();
3705
- resolve4 = null;
3761
+ if (resolve5) {
3762
+ resolve5();
3763
+ resolve5 = null;
3706
3764
  }
3707
3765
  },
3708
3766
  () => {
3709
3767
  done = true;
3710
- if (resolve4) {
3711
- resolve4();
3712
- resolve4 = null;
3768
+ if (resolve5) {
3769
+ resolve5();
3770
+ resolve5 = null;
3713
3771
  }
3714
3772
  }
3715
3773
  );
@@ -3725,7 +3783,7 @@ Rules:
3725
3783
  yield queue.shift();
3726
3784
  } else {
3727
3785
  await new Promise((r) => {
3728
- resolve4 = r;
3786
+ resolve5 = r;
3729
3787
  });
3730
3788
  }
3731
3789
  }
@@ -4073,21 +4131,21 @@ async function* tapBus(conversationId, signal) {
4073
4131
  }
4074
4132
  if (bus.done) return;
4075
4133
  if (signal.aborted) return;
4076
- await new Promise((resolve4) => {
4134
+ await new Promise((resolve5) => {
4077
4135
  const onAbort = () => {
4078
4136
  bus.listeners.delete(notify);
4079
- resolve4();
4137
+ resolve5();
4080
4138
  };
4081
4139
  const notify = () => {
4082
4140
  signal.removeEventListener("abort", onAbort);
4083
- resolve4();
4141
+ resolve5();
4084
4142
  };
4085
4143
  bus.listeners.add(notify);
4086
4144
  signal.addEventListener("abort", onAbort, { once: true });
4087
4145
  if (cursor < bus.baseIndex + bus.buffer.length || bus.done || signal.aborted) {
4088
4146
  bus.listeners.delete(notify);
4089
4147
  signal.removeEventListener("abort", onAbort);
4090
- resolve4();
4148
+ resolve5();
4091
4149
  }
4092
4150
  });
4093
4151
  }
@@ -4597,9 +4655,9 @@ async function runNodeCall(conversationId, node, systemContext, userMessage, run
4597
4655
  publish2({ type: "node_waiting_input", nodeId: node.id, question: result.tokenMessage });
4598
4656
  await readme?.(`- \u2753 [${formatTs()}] **${node.label}** \u2014 waiting for user input
4599
4657
  `);
4600
- const answer = await new Promise((resolve4, reject) => {
4658
+ const answer = await new Promise((resolve5, reject) => {
4601
4659
  const key = pendingNodeKey(conversationId, node.id);
4602
- pendingNodeInputs.set(key, resolve4);
4660
+ pendingNodeInputs.set(key, resolve5);
4603
4661
  signal.addEventListener("abort", () => {
4604
4662
  pendingNodeInputs.delete(key);
4605
4663
  reject(new Error("Aborted"));
@@ -5004,9 +5062,9 @@ ${outputs.get(node.id) ?? ""}`.trim();
5004
5062
  publish2({ type: "node_waiting_input", nodeId: node.id, question });
5005
5063
  await readme(`- \u2753 [${ts()}] **${node.label || node.id}** \u2014 waiting for user input
5006
5064
  `);
5007
- const answer = await new Promise((resolve4, reject) => {
5065
+ const answer = await new Promise((resolve5, reject) => {
5008
5066
  const key = pendingNodeKey(conversationId, node.id);
5009
- pendingNodeInputs.set(key, resolve4);
5067
+ pendingNodeInputs.set(key, resolve5);
5010
5068
  signal.addEventListener("abort", () => {
5011
5069
  pendingNodeInputs.delete(key);
5012
5070
  reject(new Error("Aborted"));
@@ -5559,21 +5617,21 @@ async function* tapJob(job, signal) {
5559
5617
  yield evt;
5560
5618
  }
5561
5619
  if (job.done || signal.aborted) return;
5562
- await new Promise((resolve4) => {
5620
+ await new Promise((resolve5) => {
5563
5621
  const onAbort = () => {
5564
5622
  job.listeners.delete(notify);
5565
- resolve4();
5623
+ resolve5();
5566
5624
  };
5567
5625
  const notify = () => {
5568
5626
  signal.removeEventListener("abort", onAbort);
5569
- resolve4();
5627
+ resolve5();
5570
5628
  };
5571
5629
  job.listeners.add(notify);
5572
5630
  signal.addEventListener("abort", onAbort, { once: true });
5573
5631
  if (cursor < job.baseIndex + job.buffer.length || job.done || signal.aborted) {
5574
5632
  job.listeners.delete(notify);
5575
5633
  signal.removeEventListener("abort", onAbort);
5576
- resolve4();
5634
+ resolve5();
5577
5635
  }
5578
5636
  });
5579
5637
  }
@@ -5725,9 +5783,9 @@ var diagnosticsRouter = router({
5725
5783
  try {
5726
5784
  while (!signal?.aborted) {
5727
5785
  if (queue.length === 0) {
5728
- await new Promise((resolve4) => {
5729
- wake = resolve4;
5730
- signal?.addEventListener("abort", () => resolve4(), { once: true });
5786
+ await new Promise((resolve5) => {
5787
+ wake = resolve5;
5788
+ signal?.addEventListener("abort", () => resolve5(), { once: true });
5731
5789
  });
5732
5790
  wake = null;
5733
5791
  continue;
@@ -5799,11 +5857,395 @@ var diagnosticsRouter = router({
5799
5857
  })
5800
5858
  });
5801
5859
 
5802
- // ../server/src/trpc/routers/kanban-queue.ts
5860
+ // ../server/src/trpc/routers/mcp-pairing.ts
5861
+ import { z as z20 } from "zod";
5862
+ import { TRPCError as TRPCError17 } from "@trpc/server";
5863
+
5864
+ // ../server/src/services/mcp-pairing.ts
5865
+ import { createHash, randomBytes as randomBytes2, randomInt, randomUUID as randomUUID5, timingSafeEqual } from "crypto";
5866
+ import { readFile as readFile3 } from "fs/promises";
5803
5867
  import { z as z19 } from "zod";
5868
+ var REQUEST_TTL_MS = 2 * 60 * 1e3;
5869
+ var MCP_TOKEN_TTL = "15m";
5870
+ var FILE = "mcp-clients.json";
5871
+ var McpClientSchema = z19.object({
5872
+ id: z19.string().uuid(),
5873
+ name: z19.string().min(1).max(100),
5874
+ cwd: z19.string().max(4096),
5875
+ /** SHA-256 du secret, en hexadécimal. */
5876
+ secretHash: z19.string().regex(/^[0-9a-f]{64}$/),
5877
+ createdAt: z19.string().max(64)
5878
+ });
5879
+ var McpClientsFileSchema = z19.object({ clients: z19.array(McpClientSchema).max(500) });
5880
+ function hashSecret(secret) {
5881
+ return createHash("sha256").update(secret).digest("hex");
5882
+ }
5883
+ var McpPairing = class {
5884
+ constructor(filePath, now = Date.now) {
5885
+ this.filePath = filePath;
5886
+ this.now = now;
5887
+ }
5888
+ requests = /* @__PURE__ */ new Map();
5889
+ /**
5890
+ * Relu à chaque accès, sans copie en mémoire. Plusieurs Runeya d'un poste
5891
+ * partagent ce fichier : une copie mémoire ferait réécrire à l'une la liste
5892
+ * d'avant l'accord donné par l'autre, et cacherait ses révocations. Le
5893
+ * fichier tient en quelques lignes ; le relire coûte moins qu'un tel oubli.
5894
+ */
5895
+ async load() {
5896
+ const path = await this.filePath();
5897
+ let text;
5898
+ try {
5899
+ text = await readFile3(path, "utf8");
5900
+ } catch (err) {
5901
+ if (err.code === "ENOENT") return [];
5902
+ throw err;
5903
+ }
5904
+ const unreadable = () => new Error(`${path} est illisible : les acc\xE8s MCP ne sont pas modifi\xE9s.`);
5905
+ let raw;
5906
+ try {
5907
+ raw = JSON.parse(text);
5908
+ } catch {
5909
+ throw unreadable();
5910
+ }
5911
+ const parsed = McpClientsFileSchema.safeParse(raw);
5912
+ if (!parsed.success) throw unreadable();
5913
+ return parsed.data.clients;
5914
+ }
5915
+ async save(clients) {
5916
+ await atomicWriteSecure(await this.filePath(), JSON.stringify({ clients }, null, 2));
5917
+ }
5918
+ sweep() {
5919
+ for (const [id, r] of this.requests) {
5920
+ if (this.now() - r.createdAt > REQUEST_TTL_MS) this.requests.delete(id);
5921
+ }
5922
+ }
5923
+ toPublic(r) {
5924
+ return { id: r.id, name: r.name, cwd: r.cwd, code: r.code, expiresAt: r.createdAt + REQUEST_TTL_MS };
5925
+ }
5926
+ /** Une demande d'accès. Refusée tant qu'une autre attend une réponse. */
5927
+ request(name, cwd) {
5928
+ this.sweep();
5929
+ if ([...this.requests.values()].some((r2) => r2.status === "pending")) {
5930
+ throw new Error("Une autre demande d'acc\xE8s attend d\xE9j\xE0 une r\xE9ponse dans Runeya.");
5931
+ }
5932
+ const r = {
5933
+ id: randomUUID5(),
5934
+ name,
5935
+ cwd,
5936
+ code: String(randomInt(0, 1e4)).padStart(4, "0"),
5937
+ createdAt: this.now(),
5938
+ status: "pending"
5939
+ };
5940
+ this.requests.set(r.id, r);
5941
+ return this.toPublic(r);
5942
+ }
5943
+ /**
5944
+ * Où en est une demande. Le secret n'est rendu qu'une fois : la demande est
5945
+ * oubliée dès qu'il est remis.
5946
+ */
5947
+ status(requestId) {
5948
+ this.sweep();
5949
+ const r = this.requests.get(requestId);
5950
+ if (!r) return { status: "expired" };
5951
+ if (r.status === "approved") {
5952
+ this.requests.delete(requestId);
5953
+ return { status: "approved", secret: r.secret };
5954
+ }
5955
+ if (r.status === "denied") this.requests.delete(requestId);
5956
+ return { status: r.status };
5957
+ }
5958
+ pending() {
5959
+ this.sweep();
5960
+ return [...this.requests.values()].filter((r) => r.status === "pending").map((r) => this.toPublic(r));
5961
+ }
5962
+ async decide(requestId, approve) {
5963
+ this.sweep();
5964
+ const r = this.requests.get(requestId);
5965
+ if (!r || r.status !== "pending") throw new Error("Cette demande n'existe plus ou a expir\xE9.");
5966
+ if (!approve) {
5967
+ r.status = "denied";
5968
+ return;
5969
+ }
5970
+ const id = randomUUID5();
5971
+ const secret = `${id}.${randomBytes2(32).toString("hex")}`;
5972
+ const clients = await this.load();
5973
+ await this.save([...clients, {
5974
+ id,
5975
+ name: r.name,
5976
+ cwd: r.cwd,
5977
+ secretHash: hashSecret(secret),
5978
+ createdAt: new Date(this.now()).toISOString()
5979
+ }]);
5980
+ r.status = "approved";
5981
+ r.secret = secret;
5982
+ }
5983
+ /** Échange le secret d'un client appairé contre un jeton court. */
5984
+ async exchange(secret) {
5985
+ const id = secret.split(".")[0] ?? "";
5986
+ const client = (await this.load()).find((c) => c.id === id);
5987
+ const expected = Buffer.from(client?.secretHash ?? "0".repeat(64), "hex");
5988
+ const match = timingSafeEqual(expected, Buffer.from(hashSecret(secret), "hex"));
5989
+ if (!client || !match) throw new Error("Appairage inconnu ou r\xE9voqu\xE9.");
5990
+ return signJwt({ mcpClientId: client.id, ttl: MCP_TOKEN_TTL });
5991
+ }
5992
+ /** Faux aussi quand le fichier est illisible : dans le doute, l'accès est refusé. */
5993
+ async isPaired(clientId) {
5994
+ return this.load().then((clients) => clients.some((c) => c.id === clientId), () => false);
5995
+ }
5996
+ async list() {
5997
+ return (await this.load()).map(({ secretHash: _, ...rest }) => rest);
5998
+ }
5999
+ async revoke(clientId) {
6000
+ const clients = await this.load();
6001
+ await this.save(clients.filter((c) => c.id !== clientId));
6002
+ }
6003
+ };
6004
+ var mcpPairing = new McpPairing(() => keyedFilePath(FILE));
6005
+
6006
+ // ../server/src/trpc/routers/mcp-pairing.ts
6007
+ function asBadRequest(fn) {
6008
+ return Promise.resolve().then(fn).catch((err) => {
6009
+ throw new TRPCError17({ code: "BAD_REQUEST", message: err.message });
6010
+ });
6011
+ }
6012
+ var mcpPairingRouter = router({
6013
+ request: publicProcedure.input(z20.object({ name: z20.string().min(1).max(100), cwd: z20.string().max(4096) })).mutation(({ ctx, input }) => {
6014
+ requireLoopback(ctx);
6015
+ if (isCiMode()) {
6016
+ throw new TRPCError17({
6017
+ code: "PRECONDITION_FAILED",
6018
+ message: "Runeya tourne en mode CI : l'appairage y est d\xE9sactiv\xE9. Passe une cl\xE9 d'API dans RUNEYA_API_TOKEN."
6019
+ });
6020
+ }
6021
+ return asBadRequest(() => mcpPairing.request(input.name, input.cwd));
6022
+ }),
6023
+ status: publicProcedure.input(z20.object({ requestId: z20.string().uuid() })).query(({ ctx, input }) => {
6024
+ requireLoopback(ctx);
6025
+ return mcpPairing.status(input.requestId);
6026
+ }),
6027
+ exchange: publicProcedure.input(z20.object({ secret: z20.string().min(1).max(200) })).mutation(({ ctx, input }) => {
6028
+ requireLoopback(ctx);
6029
+ return mcpPairing.exchange(input.secret).then((token) => ({ token })).catch(() => {
6030
+ throw new TRPCError17({ code: "UNAUTHORIZED", message: "Unknown or revoked MCP pairing." });
6031
+ });
6032
+ }),
6033
+ pending: protectedProcedure.query(({ ctx }) => {
6034
+ requireHuman(ctx);
6035
+ return mcpPairing.pending();
6036
+ }),
6037
+ decide: protectedProcedure.input(z20.object({ requestId: z20.string().uuid(), approve: z20.boolean() })).mutation(({ ctx, input }) => {
6038
+ requireHuman(ctx);
6039
+ return asBadRequest(() => mcpPairing.decide(input.requestId, input.approve));
6040
+ }),
6041
+ clients: protectedProcedure.query(({ ctx }) => {
6042
+ requireHuman(ctx);
6043
+ return mcpPairing.list();
6044
+ }),
6045
+ revoke: protectedProcedure.input(z20.object({ id: z20.string().uuid() })).mutation(({ ctx, input }) => {
6046
+ requireHuman(ctx);
6047
+ return mcpPairing.revoke(input.id);
6048
+ })
6049
+ });
6050
+
6051
+ // ../server/src/trpc/routers/ai-clients.ts
6052
+ import { z as z21 } from "zod";
6053
+ import { TRPCError as TRPCError18 } from "@trpc/server";
6054
+
6055
+ // ../server/src/services/ai-client-mcp.ts
6056
+ import { execFile as execFile4 } from "child_process";
6057
+ import { existsSync as existsSync2 } from "fs";
6058
+ import { mkdir as mkdir5, readFile as readFile4, realpath } from "fs/promises";
6059
+ import { homedir as homedir3 } from "os";
6060
+ import { dirname as dirname4, join as join7 } from "path";
6061
+ var SERVER_NAME = "runeya";
6062
+ function mcpEntry(override = env.MCP_COMMAND ?? null, home = env.HOME_DIR, platform = process.platform) {
6063
+ const [command, ...args] = override ? override.split(/\s+/) : ["runeya"];
6064
+ const envPart = home ? { env: { RUNEYA_HOME: home } } : {};
6065
+ if (platform === "win32" && command !== "node" && !/\.exe$/i.test(command)) {
6066
+ return { command: "cmd", args: ["/c", command, ...args, "mcp"], ...envPart };
6067
+ }
6068
+ return { command, args: [...args, "mcp"], ...envPart };
6069
+ }
6070
+ function envFlags(flag, entry) {
6071
+ return Object.entries(entry.env ?? {}).flatMap(([k, v]) => [flag, `${k}=${v}`]);
6072
+ }
6073
+ function vscodeUserDir(home) {
6074
+ if (process.platform === "darwin") return join7(home, "Library", "Application Support", "Code", "User");
6075
+ if (process.platform === "win32") return join7(process.env["APPDATA"] ?? join7(home, "AppData", "Roaming"), "Code", "User");
6076
+ return join7(home, ".config", "Code", "User");
6077
+ }
6078
+ function claudeDesktopDir(home) {
6079
+ if (process.platform === "darwin") return join7(home, "Library", "Application Support", "Claude");
6080
+ if (process.platform === "win32") return join7(process.env["APPDATA"] ?? join7(home, "AppData", "Roaming"), "Claude");
6081
+ return join7(home, ".config", "Claude");
6082
+ }
6083
+ var AI_CLIENTS = [
6084
+ {
6085
+ id: "claude-code",
6086
+ label: "Claude Code",
6087
+ kind: "cli",
6088
+ binary: "claude",
6089
+ configFile: (home) => join7(home, ".claude.json"),
6090
+ isInstalled: (content) => {
6091
+ try {
6092
+ const servers2 = JSON.parse(content).mcpServers;
6093
+ return Boolean(servers2 && SERVER_NAME in servers2);
6094
+ } catch {
6095
+ return false;
6096
+ }
6097
+ },
6098
+ // `-e` prend plusieurs valeurs : placé avant le nom, il l'avalerait. `--` l'arrête.
6099
+ addArgs: (e) => ["mcp", "add", "--scope", "user", SERVER_NAME, ...envFlags("-e", e), "--", e.command, ...e.args],
6100
+ removeArgs: ["mcp", "remove", "--scope", "user", SERVER_NAME]
6101
+ },
6102
+ {
6103
+ id: "codex",
6104
+ label: "Codex",
6105
+ kind: "cli",
6106
+ binary: "codex",
6107
+ configFile: (home) => join7(home, ".codex", "config.toml"),
6108
+ isInstalled: (content) => new RegExp(`^\\s*\\[mcp_servers\\.${SERVER_NAME}\\]`, "m").test(content),
6109
+ addArgs: (e) => ["mcp", "add", ...envFlags("--env", e), SERVER_NAME, "--", e.command, ...e.args],
6110
+ removeArgs: ["mcp", "remove", SERVER_NAME]
6111
+ },
6112
+ { id: "cursor", label: "Cursor", kind: "json", detectDir: (h) => join7(h, ".cursor"), configFile: (h) => join7(h, ".cursor", "mcp.json") },
6113
+ { id: "gemini", label: "Gemini CLI", kind: "json", detectDir: (h) => join7(h, ".gemini"), configFile: (h) => join7(h, ".gemini", "settings.json") },
6114
+ {
6115
+ id: "windsurf",
6116
+ label: "Windsurf",
6117
+ kind: "json",
6118
+ detectDir: (h) => join7(h, ".codeium", "windsurf"),
6119
+ configFile: (h) => join7(h, ".codeium", "windsurf", "mcp_config.json")
6120
+ },
6121
+ {
6122
+ id: "claude-desktop",
6123
+ label: "Claude Desktop",
6124
+ kind: "json",
6125
+ detectDir: claudeDesktopDir,
6126
+ configFile: (h) => join7(claudeDesktopDir(h), "claude_desktop_config.json")
6127
+ },
6128
+ {
6129
+ id: "vscode",
6130
+ label: "VS Code",
6131
+ kind: "json",
6132
+ detectDir: vscodeUserDir,
6133
+ configFile: (h) => join7(vscodeUserDir(h), "mcp.json"),
6134
+ serversKey: "servers",
6135
+ entryFields: { type: "stdio" }
6136
+ }
6137
+ ];
6138
+ async function readText(path) {
6139
+ try {
6140
+ return await readFile4(path, "utf8");
6141
+ } catch {
6142
+ return null;
6143
+ }
6144
+ }
6145
+ async function readJsonConfig(path) {
6146
+ const text = await readText(path);
6147
+ if (text === null || text.trim() === "") return {};
6148
+ const parsed = JSON.parse(text);
6149
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
6150
+ throw new Error(`${path} ne contient pas un objet JSON.`);
6151
+ }
6152
+ return parsed;
6153
+ }
6154
+ function servers(config, key = "mcpServers") {
6155
+ const value = config[key];
6156
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
6157
+ }
6158
+ async function setJsonEntry(path, present, entry = mcpEntry(), key = "mcpServers", entryFields = {}) {
6159
+ const config = await readJsonConfig(path);
6160
+ const current2 = config[key];
6161
+ if (current2 !== void 0 && (!current2 || typeof current2 !== "object" || Array.isArray(current2))) {
6162
+ throw new Error(`${path} : \xAB ${key} \xBB n'est pas un objet, le fichier n'est pas modifi\xE9.`);
6163
+ }
6164
+ const next = { ...servers(config, key) };
6165
+ if (present) next[SERVER_NAME] = { ...entryFields, ...entry };
6166
+ else delete next[SERVER_NAME];
6167
+ const target = await realpath(path).catch(() => path);
6168
+ await mkdir5(dirname4(target), { recursive: true });
6169
+ await atomicWriteSecure(target, `${JSON.stringify({ ...config, [key]: next }, null, 2)}
6170
+ `);
6171
+ }
6172
+ function run(binary, args) {
6173
+ const { command, args: fullArgs } = resolveCliInvocation(binary, args);
6174
+ return new Promise((resolve5, reject) => {
6175
+ execFile4(command, fullArgs, { timeout: 2e4, windowsHide: true }, (err, _stdout, stderr) => {
6176
+ if (err) reject(new Error(`${binary} ${args.join(" ")} : ${String(stderr).trim() || err.message}`));
6177
+ else resolve5();
6178
+ });
6179
+ });
6180
+ }
6181
+ async function isRuneyaMcpAvailable(entry = mcpEntry()) {
6182
+ const { command, args } = resolveCliInvocation(entry.command, [...entry.args.slice(0, -1), "--help"]);
6183
+ return new Promise((resolve5) => {
6184
+ execFile4(command, args, { timeout: 1e4, windowsHide: true }, (err, stdout) => {
6185
+ resolve5(!err && /^\s+mcp\s/m.test(String(stdout)));
6186
+ });
6187
+ });
6188
+ }
6189
+ async function isDetected(client, home) {
6190
+ if (client.kind === "cli") return isCliAvailable(client.binary);
6191
+ return existsSync2(client.detectDir(home));
6192
+ }
6193
+ async function isInstalled(client, home) {
6194
+ if (client.kind === "cli") {
6195
+ const content = await readText(client.configFile(home));
6196
+ return content !== null && client.isInstalled(content);
6197
+ }
6198
+ try {
6199
+ return SERVER_NAME in servers(await readJsonConfig(client.configFile(home)), client.serversKey);
6200
+ } catch {
6201
+ return false;
6202
+ }
6203
+ }
6204
+ async function listAiClients(home = homedir3()) {
6205
+ const [runeyaMcpAvailable, statuses] = await Promise.all([
6206
+ isRuneyaMcpAvailable(),
6207
+ Promise.all(AI_CLIENTS.map(async (client) => {
6208
+ if (!await isDetected(client, home)) return null;
6209
+ return { id: client.id, label: client.label, installed: await isInstalled(client, home), configFile: client.configFile(home) };
6210
+ }))
6211
+ ]);
6212
+ return { runeyaMcpAvailable, clients: statuses.filter((s) => s !== null) };
6213
+ }
6214
+ async function setAiClientMcp(id, present, home = homedir3()) {
6215
+ const client = AI_CLIENTS.find((c) => c.id === id);
6216
+ if (!client) throw new Error(`Outil IA inconnu : ${id}`);
6217
+ if (present && !await isRuneyaMcpAvailable()) {
6218
+ const { command, args } = mcpEntry();
6219
+ 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.`);
6220
+ }
6221
+ if (client.kind === "json") return setJsonEntry(client.configFile(home), present, mcpEntry(), client.serversKey, client.entryFields);
6222
+ if (await isInstalled(client, home)) await run(client.binary, client.removeArgs);
6223
+ if (present) await run(client.binary, client.addArgs(mcpEntry()));
6224
+ }
6225
+
6226
+ // ../server/src/trpc/routers/ai-clients.ts
6227
+ var AiClientIdSchema = z21.enum(["claude-code", "codex", "cursor", "gemini", "windsurf", "claude-desktop", "vscode"]);
6228
+ var aiClientsRouter = router({
6229
+ list: protectedProcedure.query(({ ctx }) => {
6230
+ requireHuman(ctx);
6231
+ return listAiClients();
6232
+ }),
6233
+ setInstalled: protectedProcedure.input(z21.object({ id: AiClientIdSchema, installed: z21.boolean() })).mutation(async ({ ctx, input }) => {
6234
+ requireHuman(ctx);
6235
+ try {
6236
+ await setAiClientMcp(input.id, input.installed);
6237
+ } catch (err) {
6238
+ throw new TRPCError18({ code: "BAD_REQUEST", message: err.message });
6239
+ }
6240
+ return listAiClients();
6241
+ })
6242
+ });
6243
+
6244
+ // ../server/src/trpc/routers/kanban-queue.ts
6245
+ import { z as z22 } from "zod";
5804
6246
 
5805
6247
  // ../server/src/services/kanban-queue.ts
5806
- import { randomUUID as randomUUID5 } from "crypto";
6248
+ import { randomUUID as randomUUID6 } from "crypto";
5807
6249
  import { EventEmitter as EventEmitter2 } from "events";
5808
6250
  var KanbanQueueService = class extends EventEmitter2 {
5809
6251
  items = [];
@@ -5831,7 +6273,7 @@ var KanbanQueueService = class extends EventEmitter2 {
5831
6273
  return { id: alreadyRunning.id, addedAt: alreadyRunning.startedAt, ...item };
5832
6274
  }
5833
6275
  const queueItem = {
5834
- id: randomUUID5(),
6276
+ id: randomUUID6(),
5835
6277
  ...item,
5836
6278
  addedAt: (/* @__PURE__ */ new Date()).toISOString()
5837
6279
  };
@@ -5859,9 +6301,9 @@ var KanbanQueueService = class extends EventEmitter2 {
5859
6301
  }
5860
6302
  const runIdx = this.running.findIndex((r) => r.id === itemId);
5861
6303
  if (runIdx >= 0) {
5862
- const run = this.running[runIdx];
5863
- abortScenario(run.conversationId);
5864
- activeSessionRegistry.kill(run.conversationId);
6304
+ const run2 = this.running[runIdx];
6305
+ abortScenario(run2.conversationId);
6306
+ activeSessionRegistry.kill(run2.conversationId);
5865
6307
  this.running.splice(runIdx, 1);
5866
6308
  this.emit("changed");
5867
6309
  this.tick();
@@ -5871,9 +6313,9 @@ var KanbanQueueService = class extends EventEmitter2 {
5871
6313
  }
5872
6314
  stopAll() {
5873
6315
  this.items = [];
5874
- for (const run of this.running) {
5875
- abortScenario(run.conversationId);
5876
- activeSessionRegistry.kill(run.conversationId);
6316
+ for (const run2 of this.running) {
6317
+ abortScenario(run2.conversationId);
6318
+ activeSessionRegistry.kill(run2.conversationId);
5877
6319
  }
5878
6320
  this.running = [];
5879
6321
  this.emit("changed");
@@ -5909,7 +6351,7 @@ var KanbanQueueService = class extends EventEmitter2 {
5909
6351
  this.tick();
5910
6352
  return;
5911
6353
  }
5912
- const companionByDefault = (await companionSettings()).enabledByDefault;
6354
+ const companionDefaults = companionCreateDefaults(await companionSettings(), item.runner);
5913
6355
  const conv = await conversationStore.create(
5914
6356
  `Queue: ${card.title}`,
5915
6357
  !!item.runner,
@@ -5921,7 +6363,7 @@ var KanbanQueueService = class extends EventEmitter2 {
5921
6363
  void 0,
5922
6364
  item.projectId,
5923
6365
  item.orgId,
5924
- companionByDefault ? { companionEnabled: true } : void 0
6366
+ Object.keys(companionDefaults).length > 0 ? companionDefaults : void 0
5925
6367
  );
5926
6368
  const cardContent = card.title + (card.markdown ? "\n\n" + card.markdown : "");
5927
6369
  if (item.scenarioId) {
@@ -5992,50 +6434,50 @@ var kanbanQueue = new KanbanQueueService();
5992
6434
  // ../server/src/trpc/routers/kanban-queue.ts
5993
6435
  var kanbanQueueRouter = router({
5994
6436
  getState: protectedProcedure.query(() => kanbanQueue.getState()),
5995
- add: protectedProcedure.input(z19.object({
5996
- boardId: z19.string().max(128),
5997
- cardId: z19.string().max(128),
5998
- runner: z19.enum(["claude-code", "codex"]).optional(),
5999
- model: z19.string().max(128).optional(),
6000
- scenarioId: z19.string().max(128).optional(),
6437
+ add: protectedProcedure.input(z22.object({
6438
+ boardId: z22.string().max(128),
6439
+ cardId: z22.string().max(128),
6440
+ runner: z22.enum(["claude-code", "codex"]).optional(),
6441
+ model: z22.string().max(128).optional(),
6442
+ scenarioId: z22.string().max(128).optional(),
6001
6443
  // Le projet ouvert quand la carte a été mise en file : la conversation
6002
6444
  // qui en naîtra lui appartient, plutôt que de rester orpheline et de
6003
6445
  // s'afficher dans tous les projets.
6004
- projectId: z19.string().max(128).optional(),
6005
- orgId: z19.string().max(128).nullable().optional()
6446
+ projectId: z22.string().max(128).optional(),
6447
+ orgId: z22.string().max(128).nullable().optional()
6006
6448
  })).mutation(({ input }) => kanbanQueue.add(input)),
6007
- addColumn: protectedProcedure.input(z19.object({
6008
- boardId: z19.string().max(128),
6009
- columnId: z19.string().max(128),
6449
+ addColumn: protectedProcedure.input(z22.object({
6450
+ boardId: z22.string().max(128),
6451
+ columnId: z22.string().max(128),
6010
6452
  // Les cartes visibles côté écran : une colonne filtrée n'envoie qu'elles.
6011
- cardIds: z19.array(z19.string().max(128)).max(5e3).optional(),
6012
- runner: z19.enum(["claude-code", "codex"]).optional(),
6013
- model: z19.string().max(128).optional(),
6014
- scenarioId: z19.string().max(128).optional(),
6015
- projectId: z19.string().max(128).optional(),
6016
- orgId: z19.string().max(128).nullable().optional()
6453
+ cardIds: z22.array(z22.string().max(128)).max(5e3).optional(),
6454
+ runner: z22.enum(["claude-code", "codex"]).optional(),
6455
+ model: z22.string().max(128).optional(),
6456
+ scenarioId: z22.string().max(128).optional(),
6457
+ projectId: z22.string().max(128).optional(),
6458
+ orgId: z22.string().max(128).nullable().optional()
6017
6459
  })).mutation(async ({ input }) => {
6018
6460
  const { boardId, columnId, ...options } = input;
6019
6461
  return kanbanQueue.addColumn(boardId, columnId, options);
6020
6462
  }),
6021
- remove: protectedProcedure.input(z19.object({ itemId: z19.string().max(128) })).mutation(({ input }) => ({ removed: kanbanQueue.remove(input.itemId) })),
6463
+ remove: protectedProcedure.input(z22.object({ itemId: z22.string().max(128) })).mutation(({ input }) => ({ removed: kanbanQueue.remove(input.itemId) })),
6022
6464
  stopAll: protectedProcedure.mutation(() => {
6023
6465
  kanbanQueue.stopAll();
6024
6466
  return { ok: true };
6025
6467
  }),
6026
- setMaxConcurrent: protectedProcedure.input(z19.object({ max: z19.number().int().min(1).max(20) })).mutation(({ input }) => {
6468
+ setMaxConcurrent: protectedProcedure.input(z22.object({ max: z22.number().int().min(1).max(20) })).mutation(({ input }) => {
6027
6469
  kanbanQueue.setMaxConcurrent(input.max);
6028
6470
  return kanbanQueue.getState();
6029
6471
  }),
6030
6472
  onChanged: protectedProcedure.subscription(async function* () {
6031
6473
  const queue = [];
6032
- let resolve4 = null;
6474
+ let resolve5 = null;
6033
6475
  const handler = () => {
6034
6476
  if (queue.length >= 50) queue.shift();
6035
6477
  queue.push(kanbanQueue.getState());
6036
- if (resolve4) {
6037
- resolve4();
6038
- resolve4 = null;
6478
+ if (resolve5) {
6479
+ resolve5();
6480
+ resolve5 = null;
6039
6481
  }
6040
6482
  };
6041
6483
  kanbanQueue.on("changed", handler);
@@ -6046,7 +6488,7 @@ var kanbanQueueRouter = router({
6046
6488
  yield queue.shift();
6047
6489
  } else {
6048
6490
  await new Promise((r) => {
6049
- resolve4 = r;
6491
+ resolve5 = r;
6050
6492
  });
6051
6493
  }
6052
6494
  }
@@ -6057,12 +6499,12 @@ var kanbanQueueRouter = router({
6057
6499
  });
6058
6500
 
6059
6501
  // ../server/src/trpc/routers/kanban.ts
6060
- import { mkdir as mkdir5, readdir, rm, stat as stat3, writeFile as writeFile4 } from "fs/promises";
6061
- import { createHash } from "crypto";
6502
+ import { mkdir as mkdir6, readdir, rm, stat as stat3, writeFile as writeFile4 } from "fs/promises";
6503
+ import { createHash as createHash2 } from "crypto";
6062
6504
  import { tmpdir } from "os";
6063
- import { join as join7 } from "path";
6064
- import { z as z20 } from "zod";
6065
- import { TRPCError as TRPCError17 } from "@trpc/server";
6505
+ import { join as join8 } from "path";
6506
+ import { z as z23 } from "zod";
6507
+ import { TRPCError as TRPCError19 } from "@trpc/server";
6066
6508
  function summarizeCard(card, boardId, columnTitle) {
6067
6509
  return {
6068
6510
  boardId,
@@ -6103,14 +6545,14 @@ function safeDecode(value) {
6103
6545
  }
6104
6546
  var MAX_ATTACHMENT_BYTES = 55 * 1024 * 1024;
6105
6547
  function attachmentCacheDir() {
6106
- return join7(tmpdir(), "runeya-kanban-attachments");
6548
+ return join8(tmpdir(), "runeya-kanban-attachments");
6107
6549
  }
6108
6550
  var ATTACHMENT_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
6109
6551
  async function purgeStaleAttachments(dir, ttlMs = ATTACHMENT_CACHE_TTL_MS) {
6110
6552
  try {
6111
6553
  const cutoff = Date.now() - ttlMs;
6112
6554
  for (const name of await readdir(dir)) {
6113
- const path = join7(dir, name);
6555
+ const path = join8(dir, name);
6114
6556
  const info = await stat3(path).catch(() => null);
6115
6557
  if (info && info.mtimeMs < cutoff) await rm(path, { force: true });
6116
6558
  }
@@ -6118,14 +6560,14 @@ async function purgeStaleAttachments(dir, ttlMs = ATTACHMENT_CACHE_TTL_MS) {
6118
6560
  }
6119
6561
  }
6120
6562
  function attachmentCacheDirs() {
6121
- return [attachmentCacheDir(), join7(machineRoot(), "kanban-attachments")];
6563
+ return [attachmentCacheDir(), join8(machineRoot(), "kanban-attachments")];
6122
6564
  }
6123
6565
  async function storeAttachment(id, name, body, dirs = attachmentCacheDirs()) {
6124
6566
  const refusals = [];
6125
6567
  for (const dir of dirs) {
6126
6568
  try {
6127
- await mkdir5(dir, { recursive: true });
6128
- const path = join7(dir, safeLocalName(id, name));
6569
+ await mkdir6(dir, { recursive: true });
6570
+ const path = join8(dir, safeLocalName(id, name));
6129
6571
  await writeFile4(path, body);
6130
6572
  void purgeStaleAttachments(dir);
6131
6573
  return path;
@@ -6133,13 +6575,13 @@ async function storeAttachment(id, name, body, dirs = attachmentCacheDirs()) {
6133
6575
  refusals.push(`${dir} (${err instanceof Error ? err.message : String(err)})`);
6134
6576
  }
6135
6577
  }
6136
- throw new TRPCError17({
6578
+ throw new TRPCError19({
6137
6579
  code: "INTERNAL_SERVER_ERROR",
6138
6580
  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.`
6139
6581
  });
6140
6582
  }
6141
6583
  function safeLocalName(id, name) {
6142
- const base = createHash("sha256").update(id).digest("hex").slice(0, 32);
6584
+ const base = createHash2("sha256").update(id).digest("hex").slice(0, 32);
6143
6585
  const ext = /\.([A-Za-z0-9]{1,12})$/.exec(name)?.[1];
6144
6586
  return ext ? `${base}.${ext.toLowerCase()}` : base;
6145
6587
  }
@@ -6154,9 +6596,9 @@ var kanbanRouter = router({
6154
6596
  * card graph, so listing them in full would be megabytes to answer "which
6155
6597
  * boards exist?". Use `getBoard` for the cards of one.
6156
6598
  */
6157
- listBoards: protectedProcedure.input(z20.object({
6158
- includeArchived: z20.boolean().optional(),
6159
- organizationId: z20.string().max(128).optional()
6599
+ listBoards: protectedProcedure.input(z23.object({
6600
+ includeArchived: z23.boolean().optional(),
6601
+ organizationId: z23.string().max(128).optional()
6160
6602
  }).optional()).query(async ({ input }) => {
6161
6603
  const { organizationId, boards } = await kanbanStore.list(
6162
6604
  input?.includeArchived ?? false,
@@ -6182,7 +6624,7 @@ var kanbanRouter = router({
6182
6624
  };
6183
6625
  }),
6184
6626
  /** One board: its columns, and the cards in each. */
6185
- getBoard: protectedProcedure.input(z20.object({ boardId: z20.string().max(128) })).query(async ({ input }) => {
6627
+ getBoard: protectedProcedure.input(z23.object({ boardId: z23.string().max(128) })).query(async ({ input }) => {
6186
6628
  const board = await kanbanStore.get(input.boardId);
6187
6629
  if (!board) return { board: null };
6188
6630
  const { links } = await kanbanStore.cardLinks(input.boardId);
@@ -6221,12 +6663,12 @@ var kanbanRouter = router({
6221
6663
  * organization — or one board when `boardId` is given. Archived boards are
6222
6664
  * left out: they are what the team decided to stop looking at.
6223
6665
  */
6224
- searchCards: protectedProcedure.input(z20.object({
6225
- query: z20.string().max(256).optional(),
6226
- createdBy: z20.string().min(1).max(200).optional(),
6227
- boardId: z20.string().max(128).optional(),
6228
- limit: z20.number().int().min(1).max(100).optional(),
6229
- organizationId: z20.string().max(128).optional()
6666
+ searchCards: protectedProcedure.input(z23.object({
6667
+ query: z23.string().max(256).optional(),
6668
+ createdBy: z23.string().min(1).max(200).optional(),
6669
+ boardId: z23.string().max(128).optional(),
6670
+ limit: z23.number().int().min(1).max(100).optional(),
6671
+ organizationId: z23.string().max(128).optional()
6230
6672
  }).refine((i) => !!i.query?.trim() || !!i.createdBy?.trim(), { message: "query or createdBy is required." })).query(async ({ input }) => {
6231
6673
  return kanbanStore.searchCards(input);
6232
6674
  }),
@@ -6234,10 +6676,10 @@ var kanbanRouter = router({
6234
6676
  * Les conversations de support liées à une carte, avec leur fil — ce que
6235
6677
  * le client a écrit à l'équipe, séparé des commentaires de la carte.
6236
6678
  */
6237
- getCardConversations: protectedProcedure.input(z20.object({
6238
- boardId: z20.string().max(128),
6239
- cardId: z20.string().max(128),
6240
- limit: z20.number().int().min(1).max(500).optional()
6679
+ getCardConversations: protectedProcedure.input(z23.object({
6680
+ boardId: z23.string().max(128),
6681
+ cardId: z23.string().max(128),
6682
+ limit: z23.number().int().min(1).max(500).optional()
6241
6683
  })).query(async ({ input }) => {
6242
6684
  const conversations = await kanbanStore.cardConversations(input.boardId, input.cardId);
6243
6685
  const limit = input.limit ?? 100;
@@ -6250,13 +6692,13 @@ var kanbanRouter = router({
6250
6692
  };
6251
6693
  }),
6252
6694
  /** Chercher une conversation chez les fournisseurs de l'organisation (lecture seule côté fournisseur). */
6253
- 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) })),
6695
+ 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) })),
6254
6696
  /** Lier une conversation trouvée à une carte. N'écrit rien chez le fournisseur. */
6255
- linkConversation: protectedProcedure.input(z20.object({
6256
- boardId: z20.string().max(128),
6257
- cardId: z20.string().max(128),
6258
- connectionId: z20.string().max(128),
6259
- externalId: z20.string().max(128)
6697
+ linkConversation: protectedProcedure.input(z23.object({
6698
+ boardId: z23.string().max(128),
6699
+ cardId: z23.string().max(128),
6700
+ connectionId: z23.string().max(128),
6701
+ externalId: z23.string().max(128)
6260
6702
  })).mutation(async ({ input }) => ({ conversations: await kanbanStore.linkConversation(input) })),
6261
6703
  /**
6262
6704
  * Le fil de commentaires d'une carte, relu à la demande.
@@ -6267,10 +6709,10 @@ var kanbanRouter = router({
6267
6709
  * qu'un `commentCount`, et rien n'en donnait le texte. Cette route est la
6268
6710
  * relecture, pour une seule carte : c'est le journal qui pèse, pas le reste.
6269
6711
  */
6270
- getCardComments: protectedProcedure.input(z20.object({
6271
- boardId: z20.string().max(128),
6272
- cardId: z20.string().max(128),
6273
- limit: z20.number().int().min(1).max(200).optional()
6712
+ getCardComments: protectedProcedure.input(z23.object({
6713
+ boardId: z23.string().max(128),
6714
+ cardId: z23.string().max(128),
6715
+ limit: z23.number().int().min(1).max(200).optional()
6274
6716
  })).query(async ({ input }) => {
6275
6717
  const history = await kanbanStore.cardHistory(input.boardId, input.cardId);
6276
6718
  const comments = history.filter((e) => e.type === "comment");
@@ -6299,18 +6741,18 @@ var kanbanRouter = router({
6299
6741
  * `getBoard` en donne déjà l'essentiel carte par carte ; cette route sert
6300
6742
  * quand c'est l'arbre lui-même qu'on veut, dates et auteurs compris.
6301
6743
  */
6302
- getCardLinks: protectedProcedure.input(z20.object({ boardId: z20.string().max(128) })).query(async ({ input }) => kanbanStore.cardLinks(input.boardId)),
6744
+ getCardLinks: protectedProcedure.input(z23.object({ boardId: z23.string().max(128) })).query(async ({ input }) => kanbanStore.cardLinks(input.boardId)),
6303
6745
  /**
6304
6746
  * Rattacher une carte à une carte parente, ou l'en détacher.
6305
6747
  *
6306
6748
  * Sans `parentCardId`, le lien est retiré. Le parent peut vivre sur un autre
6307
6749
  * tableau : `parentBoardId` le désigne alors, sinon c'est celui de la carte.
6308
6750
  */
6309
- setCardParent: protectedProcedure.input(z20.object({
6310
- boardId: z20.string().max(128),
6311
- cardId: z20.string().max(128),
6312
- parentCardId: z20.string().max(128).optional(),
6313
- parentBoardId: z20.string().max(128).optional()
6751
+ setCardParent: protectedProcedure.input(z23.object({
6752
+ boardId: z23.string().max(128),
6753
+ cardId: z23.string().max(128),
6754
+ parentCardId: z23.string().max(128).optional(),
6755
+ parentBoardId: z23.string().max(128).optional()
6314
6756
  })).mutation(async ({ input }) => {
6315
6757
  const ok = await kanbanStore.setCardParent(
6316
6758
  input.boardId,
@@ -6318,7 +6760,7 @@ var kanbanRouter = router({
6318
6760
  input.parentCardId ? { boardId: input.parentBoardId ?? input.boardId, cardId: input.parentCardId } : null
6319
6761
  );
6320
6762
  if (!ok) {
6321
- throw new TRPCError17({ code: "BAD_REQUEST", message: "The cloud refused the link (unknown card, or a cycle)." });
6763
+ throw new TRPCError19({ code: "BAD_REQUEST", message: "The cloud refused the link (unknown card, or a cycle)." });
6322
6764
  }
6323
6765
  return { ok: true };
6324
6766
  }),
@@ -6327,48 +6769,48 @@ var kanbanRouter = router({
6327
6769
  * `boardId` est le tableau où on la pose (ou d'où on la retire) ; la carte
6328
6770
  * reste sur son tableau d'origine.
6329
6771
  */
6330
- 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) })),
6331
- 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 }) => {
6772
+ 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) })),
6773
+ 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 }) => {
6332
6774
  const res = await kanbanStore.placeCard(input.boardId, input.cardId, input.column);
6333
6775
  if (!res.ok) {
6334
- throw new TRPCError17({
6776
+ throw new TRPCError19({
6335
6777
  code: "BAD_REQUEST",
6336
6778
  message: res.reason ?? "The cloud refused the placement (unknown card or column, archived card, or already on this board)."
6337
6779
  });
6338
6780
  }
6339
6781
  return { ok: true };
6340
6782
  }),
6341
- unplaceCard: protectedProcedure.input(z20.object({ boardId: z20.string().max(128), cardId: z20.string().max(128) })).mutation(async ({ input }) => {
6783
+ unplaceCard: protectedProcedure.input(z23.object({ boardId: z23.string().max(128), cardId: z23.string().max(128) })).mutation(async ({ input }) => {
6342
6784
  if (!await kanbanStore.unplaceCard(input.boardId, input.cardId)) {
6343
- throw new TRPCError17({ code: "NOT_FOUND", message: "This card is not placed on this board." });
6785
+ throw new TRPCError19({ code: "NOT_FOUND", message: "This card is not placed on this board." });
6344
6786
  }
6345
6787
  return { ok: true };
6346
6788
  }),
6347
- moveCard: protectedProcedure.input(z20.object({
6348
- boardId: z20.string().max(128),
6349
- cardId: z20.string().max(128),
6350
- targetColumn: z20.string().max(256)
6789
+ moveCard: protectedProcedure.input(z23.object({
6790
+ boardId: z23.string().max(128),
6791
+ cardId: z23.string().max(128),
6792
+ targetColumn: z23.string().max(256)
6351
6793
  })).mutation(async ({ input }) => {
6352
6794
  await executeKanbanCommands(input.boardId, input.cardId, [
6353
6795
  { type: "move_card", columnTitle: input.targetColumn }
6354
6796
  ]);
6355
6797
  return { ok: true };
6356
6798
  }),
6357
- addComment: protectedProcedure.input(z20.object({
6358
- boardId: z20.string().max(128),
6359
- cardId: z20.string().max(128),
6360
- comment: z20.string().max(1e5)
6799
+ addComment: protectedProcedure.input(z23.object({
6800
+ boardId: z23.string().max(128),
6801
+ cardId: z23.string().max(128),
6802
+ comment: z23.string().max(1e5)
6361
6803
  })).mutation(async ({ input }) => {
6362
6804
  await executeKanbanCommands(input.boardId, input.cardId, [
6363
6805
  { type: "add_comment", comment: input.comment }
6364
6806
  ]);
6365
6807
  return { ok: true };
6366
6808
  }),
6367
- addCard: protectedProcedure.input(z20.object({
6368
- boardId: z20.string().max(128),
6369
- column: z20.string().max(256),
6370
- title: z20.string().max(512),
6371
- content: z20.string().max(2e4).optional()
6809
+ addCard: protectedProcedure.input(z23.object({
6810
+ boardId: z23.string().max(128),
6811
+ column: z23.string().max(256),
6812
+ title: z23.string().max(512),
6813
+ content: z23.string().max(2e4).optional()
6372
6814
  })).mutation(async ({ input }) => {
6373
6815
  await executeKanbanBoardCommands(input.boardId, [
6374
6816
  { type: "add_card", columnTitle: input.column, cardTitle: input.title, cardContent: input.content }
@@ -6381,11 +6823,34 @@ var kanbanRouter = router({
6381
6823
  * `getBoard` rapatrie le tableau entier pour n'en regarder qu'une ; et il ne
6382
6824
  * porte pas les cartes rangées. Une carte s'adresse par son identifiant.
6383
6825
  */
6384
- getCard: protectedProcedure.input(z20.object({ boardId: z20.string().max(128), cardId: z20.string().max(128) })).query(async ({ input }) => {
6826
+ getCard: protectedProcedure.input(z23.object({ boardId: z23.string().max(128), cardId: z23.string().max(128) })).query(async ({ input }) => {
6385
6827
  const found = await kanbanStore.card(input.boardId, input.cardId);
6386
6828
  if (!found) throw new Error(`Carte ${input.cardId} introuvable sur le tableau ${input.boardId}.`);
6387
6829
  return { ...summarizeCard(found.card, input.boardId, ""), columnId: found.columnId };
6388
6830
  }),
6831
+ /**
6832
+ * Le contexte d'une carte pour un outil IA lancé hors de Runeya : le même
6833
+ * texte qu'une session lancée depuis la carte reçoit, construit au même
6834
+ * endroit (`buildKanbanCardPrompt`). L'outil le reçoit comme un message, pas
6835
+ * comme une consigne système : il guide le travail sans en avoir la force.
6836
+ */
6837
+ cardContext: protectedProcedure.input(z23.object({
6838
+ boardId: z23.string().max(128),
6839
+ cardId: z23.string().max(128),
6840
+ projectId: z23.string().max(128).optional(),
6841
+ orgId: z23.string().max(128).optional()
6842
+ })).query(async ({ input }) => {
6843
+ const board = await kanbanStore.get(input.boardId);
6844
+ if (!board) throw new TRPCError19({ code: "NOT_FOUND", message: `Tableau ${input.boardId} introuvable.` });
6845
+ if (!board.columns.some((col) => col.cards.some((c) => c.id === input.cardId))) {
6846
+ throw new TRPCError19({ code: "NOT_FOUND", message: `Carte ${input.cardId} introuvable sur le tableau ${input.boardId}.` });
6847
+ }
6848
+ const scope = {
6849
+ ...input.projectId ? { projectId: input.projectId } : {},
6850
+ ...input.orgId ? { orgId: input.orgId } : {}
6851
+ };
6852
+ return { context: await buildKanbanCardPrompt(board, input.cardId, scope) };
6853
+ }),
6389
6854
  /**
6390
6855
  * Modifier une carte.
6391
6856
  *
@@ -6395,16 +6860,16 @@ var kanbanRouter = router({
6395
6860
  * Un champ absent n'est pas touché ; `null` l'efface. La distinction compte —
6396
6861
  * changer un titre ne doit pas vider une couleur.
6397
6862
  */
6398
- updateCard: protectedProcedure.input(z20.object({
6399
- boardId: z20.string().max(128),
6400
- cardId: z20.string().max(128),
6401
- title: z20.string().min(1).max(512).optional(),
6402
- content: z20.string().max(1e5).optional(),
6403
- priority: z20.number().int().min(1).max(10).nullable().optional(),
6404
- color: z20.string().max(32).nullable().optional(),
6405
- groups: z20.array(z20.string().min(1).max(60)).max(30).optional(),
6406
- deadline: z20.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().optional(),
6407
- assignees: z20.array(z20.string().max(128)).max(50).optional()
6863
+ updateCard: protectedProcedure.input(z23.object({
6864
+ boardId: z23.string().max(128),
6865
+ cardId: z23.string().max(128),
6866
+ title: z23.string().min(1).max(512).optional(),
6867
+ content: z23.string().max(1e5).optional(),
6868
+ priority: z23.number().int().min(1).max(10).nullable().optional(),
6869
+ color: z23.string().max(32).nullable().optional(),
6870
+ groups: z23.array(z23.string().min(1).max(60)).max(30).optional(),
6871
+ deadline: z23.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().optional(),
6872
+ assignees: z23.array(z23.string().max(128)).max(50).optional()
6408
6873
  })).mutation(async ({ input }) => {
6409
6874
  const { boardId, cardId, content, ...rest } = input;
6410
6875
  const patch = { ...rest };
@@ -6415,10 +6880,10 @@ var kanbanRouter = router({
6415
6880
  return summarizeCard(card, boardId, "");
6416
6881
  }),
6417
6882
  /** Ranger une carte, ou la reprendre. */
6418
- setCardArchived: protectedProcedure.input(z20.object({
6419
- boardId: z20.string().max(128),
6420
- cardId: z20.string().max(128),
6421
- archived: z20.boolean()
6883
+ setCardArchived: protectedProcedure.input(z23.object({
6884
+ boardId: z23.string().max(128),
6885
+ cardId: z23.string().max(128),
6886
+ archived: z23.boolean()
6422
6887
  })).mutation(async ({ input }) => {
6423
6888
  const ok = await kanbanStore.setCardArchived(input.boardId, input.cardId, input.archived);
6424
6889
  if (!ok) throw new Error(`La carte ${input.cardId} n'a pas pu \xEAtre ${input.archived ? "archiv\xE9e" : "reprise"}.`);
@@ -6430,48 +6895,48 @@ var kanbanRouter = router({
6430
6895
  * Sans cette liste, assigner revient à deviner un identifiant — et l'agent
6431
6896
  * n'a aucun moyen de le trouver.
6432
6897
  */
6433
- listMembers: protectedProcedure.input(z20.object({ organizationId: z20.string().max(128).optional() })).query(async ({ input }) => ({ members: await kanbanStore.members(input.organizationId) })),
6898
+ listMembers: protectedProcedure.input(z23.object({ organizationId: z23.string().max(128).optional() })).query(async ({ input }) => ({ members: await kanbanStore.members(input.organizationId) })),
6434
6899
  /**
6435
6900
  * Fetch one card attachment from the cloud and drop it on this machine, so an
6436
6901
  * agent can open it with its own file tools. The bytes never go through the
6437
6902
  * prompt: the card context lists the attachments, this returns a path.
6438
6903
  */
6439
- downloadAttachment: protectedProcedure.input(z20.object({
6440
- attachmentId: z20.string().max(128),
6904
+ downloadAttachment: protectedProcedure.input(z23.object({
6905
+ attachmentId: z23.string().max(128),
6441
6906
  // La pièce jointe appartient à une organisation : la nommer dit sous quel
6442
6907
  // compte la demander.
6443
- orgId: z20.string().max(128).optional(),
6908
+ orgId: z23.string().max(128).optional(),
6444
6909
  // À défaut, le tableau d'où vient la carte suffit : il désigne un compte
6445
6910
  // comme le fait n'importe quel autre appel du kanban. Sans lui, la route
6446
6911
  // retombait sur le compte unique du poste et refusait de servir dès qu'il
6447
6912
  // y en avait plusieurs — alors que l'agent, qui n'a qu'un identifiant de
6448
6913
  // fichier, n'avait aucun moyen de nommer l'organisation.
6449
- boardId: z20.string().max(128).optional()
6914
+ boardId: z23.string().max(128).optional()
6450
6915
  })).mutation(async ({ input }) => {
6451
6916
  const id = input.attachmentId;
6452
6917
  if (!ATTACHMENT_ID_REGEX.test(id)) {
6453
- throw new TRPCError17({ code: "BAD_REQUEST", message: "Invalid attachment id" });
6918
+ throw new TRPCError19({ code: "BAD_REQUEST", message: "Invalid attachment id" });
6454
6919
  }
6455
6920
  const res = await cloudFetch2(`/api/kanban/file/${id}`, {
6456
6921
  timeoutMs: 12e4,
6457
6922
  accountId: await attachmentScope(input)
6458
6923
  });
6459
6924
  if (res.status === 404) {
6460
- throw new TRPCError17({ code: "NOT_FOUND", message: `Attachment ${id} not found` });
6925
+ throw new TRPCError19({ code: "NOT_FOUND", message: `Attachment ${id} not found` });
6461
6926
  }
6462
6927
  if (!res.ok) {
6463
- throw new TRPCError17({
6928
+ throw new TRPCError19({
6464
6929
  code: res.status === 403 ? "FORBIDDEN" : "INTERNAL_SERVER_ERROR",
6465
6930
  message: `Cloud refused the attachment (HTTP ${res.status})`
6466
6931
  });
6467
6932
  }
6468
6933
  const declared = Number(res.headers.get("content-length") ?? 0);
6469
6934
  if (declared > MAX_ATTACHMENT_BYTES) {
6470
- throw new TRPCError17({ code: "PAYLOAD_TOO_LARGE", message: "Attachment too large to download" });
6935
+ throw new TRPCError19({ code: "PAYLOAD_TOO_LARGE", message: "Attachment too large to download" });
6471
6936
  }
6472
6937
  const body = Buffer.from(await res.arrayBuffer());
6473
6938
  if (body.length > MAX_ATTACHMENT_BYTES) {
6474
- throw new TRPCError17({ code: "PAYLOAD_TOO_LARGE", message: "Attachment too large to download" });
6939
+ throw new TRPCError19({ code: "PAYLOAD_TOO_LARGE", message: "Attachment too large to download" });
6475
6940
  }
6476
6941
  const contentType = res.headers.get("content-type") ?? "application/octet-stream";
6477
6942
  const disposition = res.headers.get("content-disposition") ?? "";
@@ -6483,21 +6948,21 @@ var kanbanRouter = router({
6483
6948
  });
6484
6949
 
6485
6950
  // ../server/src/trpc/routers/terminal.ts
6486
- import { z as z21 } from "zod";
6487
- import { TRPCError as TRPCError18 } from "@trpc/server";
6951
+ import { z as z24 } from "zod";
6952
+ import { TRPCError as TRPCError20 } from "@trpc/server";
6488
6953
  var terminalRouter = router({
6489
6954
  /** Create a PTY shell session on the target agent */
6490
- create: protectedProcedure.input(z21.object({
6491
- agentId: z21.string().max(128),
6492
- cwd: z21.string().max(512).optional(),
6955
+ create: protectedProcedure.input(z24.object({
6956
+ agentId: z24.string().max(128),
6957
+ cwd: z24.string().max(512).optional(),
6493
6958
  /** Anchors the terminal when no explicit cwd is given. */
6494
- serviceId: z21.string().max(128).optional(),
6495
- projectId: z21.string().max(128).optional(),
6959
+ serviceId: z24.string().max(128).optional(),
6960
+ projectId: z24.string().max(128).optional(),
6496
6961
  /** L'organisation du projet : un id seul peut en nommer un par organisation. */
6497
- orgId: z21.string().max(128).nullable().optional(),
6498
- cols: z21.number().int().min(1).max(512).optional(),
6499
- rows: z21.number().int().min(1).max(256).optional(),
6500
- title: z21.string().max(128).optional()
6962
+ orgId: z24.string().max(128).nullable().optional(),
6963
+ cols: z24.number().int().min(1).max(512).optional(),
6964
+ rows: z24.number().int().min(1).max(256).optional(),
6965
+ title: z24.string().max(128).optional()
6501
6966
  })).mutation(async ({ input }) => {
6502
6967
  const result = await agentManager.agentMutation(
6503
6968
  input.agentId,
@@ -6518,17 +6983,17 @@ var terminalRouter = router({
6518
6983
  return result;
6519
6984
  }),
6520
6985
  /** Les sessions PTY encore vivantes sur l'agent (onglets à retrouver) */
6521
- list: protectedProcedure.input(z21.object({ agentId: z21.string().max(128) })).query(async ({ input }) => {
6986
+ list: protectedProcedure.input(z24.object({ agentId: z24.string().max(128) })).query(async ({ input }) => {
6522
6987
  return agentManager.agentQuery(
6523
6988
  input.agentId,
6524
6989
  "terminal.list"
6525
6990
  );
6526
6991
  }),
6527
6992
  /** Renommer un onglet */
6528
- rename: protectedProcedure.input(z21.object({
6529
- agentId: z21.string().max(128),
6530
- sessionId: z21.string().max(128),
6531
- title: z21.string().max(128)
6993
+ rename: protectedProcedure.input(z24.object({
6994
+ agentId: z24.string().max(128),
6995
+ sessionId: z24.string().max(128),
6996
+ title: z24.string().max(128)
6532
6997
  })).mutation(async ({ input }) => {
6533
6998
  await agentManager.agentMutation(
6534
6999
  input.agentId,
@@ -6538,10 +7003,10 @@ var terminalRouter = router({
6538
7003
  return { ok: true };
6539
7004
  }),
6540
7005
  /** Send keystrokes / data to a PTY session */
6541
- write: protectedProcedure.input(z21.object({
6542
- agentId: z21.string().max(128),
6543
- sessionId: z21.string().max(128),
6544
- data: z21.string().max(65536)
7006
+ write: protectedProcedure.input(z24.object({
7007
+ agentId: z24.string().max(128),
7008
+ sessionId: z24.string().max(128),
7009
+ data: z24.string().max(65536)
6545
7010
  })).mutation(async ({ input }) => {
6546
7011
  await agentManager.agentMutation(
6547
7012
  input.agentId,
@@ -6551,11 +7016,11 @@ var terminalRouter = router({
6551
7016
  return { ok: true };
6552
7017
  }),
6553
7018
  /** Resize the PTY */
6554
- resize: protectedProcedure.input(z21.object({
6555
- agentId: z21.string().max(128),
6556
- sessionId: z21.string().max(128),
6557
- cols: z21.number().int().min(1).max(512),
6558
- rows: z21.number().int().min(1).max(256)
7019
+ resize: protectedProcedure.input(z24.object({
7020
+ agentId: z24.string().max(128),
7021
+ sessionId: z24.string().max(128),
7022
+ cols: z24.number().int().min(1).max(512),
7023
+ rows: z24.number().int().min(1).max(256)
6559
7024
  })).mutation(async ({ input }) => {
6560
7025
  await agentManager.agentMutation(
6561
7026
  input.agentId,
@@ -6565,9 +7030,9 @@ var terminalRouter = router({
6565
7030
  return { ok: true };
6566
7031
  }),
6567
7032
  /** Destroy a PTY session */
6568
- destroy: protectedProcedure.input(z21.object({
6569
- agentId: z21.string().max(128),
6570
- sessionId: z21.string().max(128)
7033
+ destroy: protectedProcedure.input(z24.object({
7034
+ agentId: z24.string().max(128),
7035
+ sessionId: z24.string().max(128)
6571
7036
  })).mutation(async ({ input }) => {
6572
7037
  await agentManager.agentMutation(
6573
7038
  input.agentId,
@@ -6577,14 +7042,14 @@ var terminalRouter = router({
6577
7042
  return { ok: true };
6578
7043
  }),
6579
7044
  /** Stream PTY output — proxied via AgentWSBridge dynamic subscription */
6580
- onData: protectedProcedure.input(z21.object({
6581
- agentId: z21.string().max(128),
6582
- sessionId: z21.string().max(128),
7045
+ onData: protectedProcedure.input(z24.object({
7046
+ agentId: z24.string().max(128),
7047
+ sessionId: z24.string().max(128),
6583
7048
  /** Rejouer l'historique de la session avant le flux temps réel. */
6584
- replay: z21.boolean().optional()
7049
+ replay: z24.boolean().optional()
6585
7050
  })).subscription(async function* ({ input }) {
6586
7051
  const queue = [];
6587
- let resolve4 = null;
7052
+ let resolve5 = null;
6588
7053
  let done = false;
6589
7054
  let unsubscribe;
6590
7055
  try {
@@ -6594,21 +7059,21 @@ var terminalRouter = router({
6594
7059
  { sessionId: input.sessionId, replay: input.replay },
6595
7060
  (data) => {
6596
7061
  if (typeof data === "string") queue.push(data);
6597
- if (resolve4) {
6598
- resolve4();
6599
- resolve4 = null;
7062
+ if (resolve5) {
7063
+ resolve5();
7064
+ resolve5 = null;
6600
7065
  }
6601
7066
  },
6602
7067
  () => {
6603
7068
  done = true;
6604
- if (resolve4) {
6605
- resolve4();
6606
- resolve4 = null;
7069
+ if (resolve5) {
7070
+ resolve5();
7071
+ resolve5 = null;
6607
7072
  }
6608
7073
  }
6609
7074
  );
6610
7075
  } catch (err) {
6611
- throw new TRPCError18({
7076
+ throw new TRPCError20({
6612
7077
  code: "BAD_REQUEST",
6613
7078
  message: `Cannot subscribe to terminal: ${err.message}`
6614
7079
  });
@@ -6619,7 +7084,7 @@ var terminalRouter = router({
6619
7084
  yield queue.shift();
6620
7085
  } else {
6621
7086
  await new Promise((r) => {
6622
- resolve4 = r;
7087
+ resolve5 = r;
6623
7088
  });
6624
7089
  }
6625
7090
  }
@@ -6630,27 +7095,27 @@ var terminalRouter = router({
6630
7095
  });
6631
7096
 
6632
7097
  // ../server/src/trpc/routers/utility.ts
6633
- import { z as z22 } from "zod";
7098
+ import { z as z25 } from "zod";
6634
7099
  var MAX_WAIT_MS = 5 * 60 * 1e3;
6635
7100
  var utilityRouter = router({
6636
- wait: protectedProcedure.input(z22.object({
6637
- ms: z22.number().int().min(0).max(MAX_WAIT_MS)
7101
+ wait: protectedProcedure.input(z25.object({
7102
+ ms: z25.number().int().min(0).max(MAX_WAIT_MS)
6638
7103
  })).mutation(async ({ input }) => {
6639
- await new Promise((resolve4) => setTimeout(resolve4, input.ms));
7104
+ await new Promise((resolve5) => setTimeout(resolve5, input.ms));
6640
7105
  return { waited: input.ms };
6641
7106
  })
6642
7107
  });
6643
7108
 
6644
7109
  // ../server/src/trpc/routers/wiki.ts
6645
- import { z as z23 } from "zod";
6646
- import { TRPCError as TRPCError19 } from "@trpc/server";
6647
- import { readFile as readFile3, readdir as readdir2, stat as stat4, realpath, writeFile as writeFile5, mkdir as mkdir6, rename, rm as rm2 } from "fs/promises";
6648
- import { resolve as resolvePath, join as join8, relative as relative2, sep, dirname as dirname4 } from "path";
6649
- var SCOPE_SCHEMA = z23.union([
6650
- z23.literal("global"),
6651
- z23.string().regex(/^service:[A-Za-z0-9_-]{1,128}$/, "Invalid service scope")
7110
+ import { z as z26 } from "zod";
7111
+ import { TRPCError as TRPCError21 } from "@trpc/server";
7112
+ 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";
7113
+ import { resolve as resolvePath, join as join9, relative as relative2, sep, dirname as dirname5 } from "path";
7114
+ var SCOPE_SCHEMA = z26.union([
7115
+ z26.literal("global"),
7116
+ z26.string().regex(/^service:[A-Za-z0-9_-]{1,128}$/, "Invalid service scope")
6652
7117
  ]);
6653
- var REL_PATH_SCHEMA = z23.string().min(1).max(512).regex(/^[A-Za-z0-9_./-]+$/).refine((p) => !p.includes("..") && !p.startsWith("/"), {
7118
+ var REL_PATH_SCHEMA = z26.string().min(1).max(512).regex(/^[A-Za-z0-9_./-]+$/).refine((p) => !p.includes("..") && !p.startsWith("/"), {
6654
7119
  message: "Path must be a relative path without traversal"
6655
7120
  });
6656
7121
  var MAX_FILES = 500;
@@ -6670,36 +7135,36 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
6670
7135
  "target",
6671
7136
  "out"
6672
7137
  ]);
6673
- var DocsEntrySchema = z23.lazy(() => z23.union([
6674
- z23.object({ type: z23.literal("file"), name: z23.string(), path: z23.string() }),
6675
- z23.object({ type: z23.literal("dir"), name: z23.string(), path: z23.string(), children: z23.array(DocsEntrySchema) })
7138
+ var DocsEntrySchema = z26.lazy(() => z26.union([
7139
+ z26.object({ type: z26.literal("file"), name: z26.string(), path: z26.string() }),
7140
+ z26.object({ type: z26.literal("dir"), name: z26.string(), path: z26.string(), children: z26.array(DocsEntrySchema) })
6676
7141
  ]));
6677
7142
  async function resolveScope(scope) {
6678
7143
  if (scope === "global") {
6679
7144
  const settings = await settingsManager.getSettings();
6680
7145
  const sub = (settings.globalDocsPath?.trim() || "wiki").replace(/^[\\/]+|[\\/]+$/g, "");
6681
7146
  if (sub.includes("..") || sub.includes("\0")) {
6682
- throw new TRPCError19({ code: "BAD_REQUEST", message: "Sous-chemin invalide" });
7147
+ throw new TRPCError21({ code: "BAD_REQUEST", message: "Sous-chemin invalide" });
6683
7148
  }
6684
7149
  const dataDirAbs = resolvePath(env.DATA_DIR);
6685
- await mkdir6(dataDirAbs, { recursive: true });
6686
- const base = dirname4(await realpath(dataDirAbs));
7150
+ await mkdir7(dataDirAbs, { recursive: true });
7151
+ const base = dirname5(await realpath2(dataDirAbs));
6687
7152
  const target = resolvePath(base, sub);
6688
7153
  if (target !== base && !(target + sep).startsWith(base + sep)) {
6689
- throw new TRPCError19({ code: "FORBIDDEN", message: "Hors du dossier de donn\xE9es du serveur" });
7154
+ throw new TRPCError21({ code: "FORBIDDEN", message: "Hors du dossier de donn\xE9es du serveur" });
6690
7155
  }
6691
- await mkdir6(target, { recursive: true });
6692
- const root = await realpath(target);
7156
+ await mkdir7(target, { recursive: true });
7157
+ const root = await realpath2(target);
6693
7158
  if (root !== base && !(root + sep).startsWith(base + sep)) {
6694
- throw new TRPCError19({ code: "FORBIDDEN", message: "Le dossier wiki r\xE9sout hors du dossier de donn\xE9es" });
7159
+ throw new TRPCError21({ code: "FORBIDDEN", message: "Le dossier wiki r\xE9sout hors du dossier de donn\xE9es" });
6695
7160
  }
6696
7161
  return { mode: "local", root };
6697
7162
  }
6698
7163
  const serviceId = scope.slice("service:".length);
6699
7164
  const service = await serviceStore.get(serviceId);
6700
- if (!service) throw new TRPCError19({ code: "NOT_FOUND", message: "Service introuvable" });
7165
+ if (!service) throw new TRPCError21({ code: "NOT_FOUND", message: "Service introuvable" });
6701
7166
  const agentId = await resolveEffectiveAgentId(service);
6702
- if (!agentId) throw new TRPCError19({ code: "BAD_REQUEST", message: "Aucun agent configur\xE9 pour ce service" });
7167
+ if (!agentId) throw new TRPCError21({ code: "BAD_REQUEST", message: "Aucun agent configur\xE9 pour ce service" });
6703
7168
  const cwd = await resolveWorkspaceCwd({ serviceId, override: service.docsPath?.trim() || void 0 });
6704
7169
  return { mode: "agent", agentId, cwd };
6705
7170
  }
@@ -6715,7 +7180,7 @@ async function walkLocal(root, dir, depth, counter) {
6715
7180
  for (const e of entries2) {
6716
7181
  if (counter.n >= MAX_FILES) break;
6717
7182
  if (e.name.startsWith(".") && e.name !== ".") continue;
6718
- const abs = join8(dir, e.name);
7183
+ const abs = join9(dir, e.name);
6719
7184
  const rel = relative2(root, abs).split(sep).join("/");
6720
7185
  if (e.isDirectory()) {
6721
7186
  if (IGNORED_DIRS.has(e.name)) continue;
@@ -6734,35 +7199,35 @@ async function walkLocal(root, dir, depth, counter) {
6734
7199
  }
6735
7200
  async function resolveSafeLocal(root, relPath, requireExist) {
6736
7201
  if (relPath.includes("..") || relPath.startsWith("/")) {
6737
- throw new TRPCError19({ code: "BAD_REQUEST", message: "Chemin invalide" });
7202
+ throw new TRPCError21({ code: "BAD_REQUEST", message: "Chemin invalide" });
6738
7203
  }
6739
7204
  const target = resolvePath(root, relPath);
6740
7205
  if (requireExist) {
6741
7206
  let real;
6742
7207
  try {
6743
- real = await realpath(target);
7208
+ real = await realpath2(target);
6744
7209
  } catch {
6745
- throw new TRPCError19({ code: "NOT_FOUND", message: "Chemin introuvable" });
7210
+ throw new TRPCError21({ code: "NOT_FOUND", message: "Chemin introuvable" });
6746
7211
  }
6747
7212
  if (real !== root && !(real + sep).startsWith(root + sep)) {
6748
- throw new TRPCError19({ code: "FORBIDDEN", message: "Hors du dossier autoris\xE9" });
7213
+ throw new TRPCError21({ code: "FORBIDDEN", message: "Hors du dossier autoris\xE9" });
6749
7214
  }
6750
7215
  return real;
6751
7216
  }
6752
- const parent = dirname4(target);
7217
+ const parent = dirname5(target);
6753
7218
  let realParent;
6754
7219
  try {
6755
- realParent = await realpath(parent);
7220
+ realParent = await realpath2(parent);
6756
7221
  } catch {
6757
7222
  realParent = parent;
6758
7223
  }
6759
7224
  if (realParent !== root && !(realParent + sep).startsWith(root + sep)) {
6760
- throw new TRPCError19({ code: "FORBIDDEN", message: "Hors du dossier autoris\xE9" });
7225
+ throw new TRPCError21({ code: "FORBIDDEN", message: "Hors du dossier autoris\xE9" });
6761
7226
  }
6762
7227
  return resolvePath(realParent, target.slice(parent.length).replace(/^[\\/]+/, ""));
6763
7228
  }
6764
7229
  var wikiRouter = router({
6765
- list: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA })).output(z23.object({ root: z23.string(), entries: z23.array(DocsEntrySchema), truncated: z23.boolean() })).query(async ({ input }) => {
7230
+ list: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA })).output(z26.object({ root: z26.string(), entries: z26.array(DocsEntrySchema), truncated: z26.boolean() })).query(async ({ input }) => {
6766
7231
  const target = await resolveScope(input.scope);
6767
7232
  if (target.mode === "agent") {
6768
7233
  return agentManager.agentQuery(target.agentId, "wiki.listRepoMarkdown", { cwd: target.cwd });
@@ -6771,9 +7236,9 @@ var wikiRouter = router({
6771
7236
  const entries2 = await walkLocal(target.root, target.root, 0, counter);
6772
7237
  return { root: target.root, entries: entries2, truncated: counter.n >= MAX_FILES };
6773
7238
  }),
6774
- read: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).output(z23.object({ path: z23.string(), content: z23.string() })).query(async ({ input }) => {
7239
+ read: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).output(z26.object({ path: z26.string(), content: z26.string() })).query(async ({ input }) => {
6775
7240
  if (!/\.(md|markdown)$/i.test(input.path)) {
6776
- throw new TRPCError19({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
7241
+ throw new TRPCError21({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
6777
7242
  }
6778
7243
  const target = await resolveScope(input.scope);
6779
7244
  if (target.mode === "agent") {
@@ -6781,27 +7246,27 @@ var wikiRouter = router({
6781
7246
  }
6782
7247
  const file = await resolveSafeLocal(target.root, input.path, true);
6783
7248
  const s = await stat4(file);
6784
- if (!s.isFile()) throw new TRPCError19({ code: "BAD_REQUEST", message: "Pas un fichier" });
6785
- if (s.size > MAX_FILE_BYTES) throw new TRPCError19({ code: "PAYLOAD_TOO_LARGE", message: "Fichier trop volumineux" });
6786
- const content = await readFile3(file, "utf-8");
7249
+ if (!s.isFile()) throw new TRPCError21({ code: "BAD_REQUEST", message: "Pas un fichier" });
7250
+ if (s.size > MAX_FILE_BYTES) throw new TRPCError21({ code: "PAYLOAD_TOO_LARGE", message: "Fichier trop volumineux" });
7251
+ const content = await readFile5(file, "utf-8");
6787
7252
  return { path: input.path, content };
6788
7253
  }),
6789
- write: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA, content: z23.string().max(5e6) })).mutation(async ({ input }) => {
7254
+ write: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA, content: z26.string().max(5e6) })).mutation(async ({ input }) => {
6790
7255
  if (!/\.(md|markdown)$/i.test(input.path)) {
6791
- throw new TRPCError19({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
7256
+ throw new TRPCError21({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
6792
7257
  }
6793
7258
  const target = await resolveScope(input.scope);
6794
7259
  if (target.mode === "agent") {
6795
7260
  return agentManager.agentMutation(target.agentId, "wiki.writeMarkdown", { cwd: target.cwd, path: input.path, content: input.content });
6796
7261
  }
6797
7262
  const file = await resolveSafeLocal(target.root, input.path, false);
6798
- await mkdir6(dirname4(file), { recursive: true });
7263
+ await mkdir7(dirname5(file), { recursive: true });
6799
7264
  await writeFile5(file, input.content, "utf-8");
6800
7265
  return { path: input.path, ok: true };
6801
7266
  }),
6802
- createFile: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
7267
+ createFile: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
6803
7268
  if (!/\.(md|markdown)$/i.test(input.path)) {
6804
- throw new TRPCError19({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
7269
+ throw new TRPCError21({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
6805
7270
  }
6806
7271
  const target = await resolveScope(input.scope);
6807
7272
  if (target.mode === "agent") {
@@ -6810,24 +7275,24 @@ var wikiRouter = router({
6810
7275
  const file = await resolveSafeLocal(target.root, input.path, false);
6811
7276
  try {
6812
7277
  await stat4(file);
6813
- throw new TRPCError19({ code: "CONFLICT", message: "Fichier d\xE9j\xE0 existant" });
7278
+ throw new TRPCError21({ code: "CONFLICT", message: "Fichier d\xE9j\xE0 existant" });
6814
7279
  } catch (err) {
6815
- if (err instanceof TRPCError19) throw err;
7280
+ if (err instanceof TRPCError21) throw err;
6816
7281
  }
6817
- await mkdir6(dirname4(file), { recursive: true });
7282
+ await mkdir7(dirname5(file), { recursive: true });
6818
7283
  await writeFile5(file, "", "utf-8");
6819
7284
  return { path: input.path, ok: true };
6820
7285
  }),
6821
- createDir: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
7286
+ createDir: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
6822
7287
  const target = await resolveScope(input.scope);
6823
7288
  if (target.mode === "agent") {
6824
7289
  return agentManager.agentMutation(target.agentId, "wiki.createDir", { cwd: target.cwd, path: input.path });
6825
7290
  }
6826
7291
  const dir = await resolveSafeLocal(target.root, input.path, false);
6827
- await mkdir6(dir, { recursive: true });
7292
+ await mkdir7(dir, { recursive: true });
6828
7293
  return { path: input.path, ok: true };
6829
7294
  }),
6830
- rename: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, fromPath: REL_PATH_SCHEMA, toPath: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
7295
+ rename: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, fromPath: REL_PATH_SCHEMA, toPath: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
6831
7296
  const target = await resolveScope(input.scope);
6832
7297
  if (target.mode === "agent") {
6833
7298
  return agentManager.agentMutation(target.agentId, "wiki.renamePath", { cwd: target.cwd, fromPath: input.fromPath, toPath: input.toPath });
@@ -6836,34 +7301,34 @@ var wikiRouter = router({
6836
7301
  const to = await resolveSafeLocal(target.root, input.toPath, false);
6837
7302
  try {
6838
7303
  await stat4(to);
6839
- throw new TRPCError19({ code: "CONFLICT", message: "La cible existe d\xE9j\xE0" });
7304
+ throw new TRPCError21({ code: "CONFLICT", message: "La cible existe d\xE9j\xE0" });
6840
7305
  } catch (err) {
6841
- if (err instanceof TRPCError19) throw err;
7306
+ if (err instanceof TRPCError21) throw err;
6842
7307
  }
6843
- await mkdir6(dirname4(to), { recursive: true });
7308
+ await mkdir7(dirname5(to), { recursive: true });
6844
7309
  await rename(from, to);
6845
7310
  return { fromPath: input.fromPath, toPath: input.toPath, ok: true };
6846
7311
  }),
6847
- ensureRoot: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA })).mutation(async ({ input }) => {
7312
+ ensureRoot: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA })).mutation(async ({ input }) => {
6848
7313
  if (input.scope === "global") {
6849
7314
  await resolveScope("global");
6850
7315
  return { ok: true };
6851
7316
  }
6852
7317
  const serviceId = input.scope.slice("service:".length);
6853
7318
  const service = await serviceStore.get(serviceId);
6854
- if (!service) throw new TRPCError19({ code: "NOT_FOUND", message: "Service introuvable" });
7319
+ if (!service) throw new TRPCError21({ code: "NOT_FOUND", message: "Service introuvable" });
6855
7320
  const agentId = await resolveEffectiveAgentId(service);
6856
- if (!agentId) throw new TRPCError19({ code: "BAD_REQUEST", message: "Aucun agent configur\xE9" });
7321
+ if (!agentId) throw new TRPCError21({ code: "BAD_REQUEST", message: "Aucun agent configur\xE9" });
6857
7322
  const cwd = await resolveWorkspaceCwd({ serviceId, override: service.docsPath?.trim() || void 0 });
6858
7323
  return agentManager.agentMutation(agentId, "wiki.ensureRoot", { cwd });
6859
7324
  }),
6860
- delete: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
7325
+ delete: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
6861
7326
  const target = await resolveScope(input.scope);
6862
7327
  if (target.mode === "agent") {
6863
7328
  return agentManager.agentMutation(target.agentId, "wiki.deletePath", { cwd: target.cwd, path: input.path });
6864
7329
  }
6865
7330
  const fp = await resolveSafeLocal(target.root, input.path, true);
6866
- if (fp === target.root) throw new TRPCError19({ code: "FORBIDDEN", message: "Impossible de supprimer la racine" });
7331
+ if (fp === target.root) throw new TRPCError21({ code: "FORBIDDEN", message: "Impossible de supprimer la racine" });
6867
7332
  await rm2(fp, { recursive: true, force: true });
6868
7333
  return { path: input.path, ok: true };
6869
7334
  })
@@ -6878,19 +7343,21 @@ var configRouter = router({
6878
7343
  /** How this instance was launched, and where it answers. */
6879
7344
  app: publicProcedure.query(() => ({
6880
7345
  fromDesktop: env.FROM_DESKTOP,
6881
- port: env.PORT
7346
+ port: env.PORT,
7347
+ /** Projet figé par `--project`, ou null. */
7348
+ projectId: env.PROJECT_ID
6882
7349
  }))
6883
7350
  });
6884
7351
 
6885
7352
  // ../server/src/trpc/routers/cloud-sync.ts
6886
- import { z as z24 } from "zod";
6887
- import { TRPCError as TRPCError20 } from "@trpc/server";
7353
+ import { z as z27 } from "zod";
7354
+ import { TRPCError as TRPCError22 } from "@trpc/server";
6888
7355
 
6889
7356
  // ../server/src/services/org-sync-scope.ts
6890
7357
  import { access } from "fs/promises";
6891
- import { join as join9 } from "path";
7358
+ import { join as join10 } from "path";
6892
7359
  async function isMaterialized(root) {
6893
- return access(join9(root, "project.json")).then(() => true, () => false);
7360
+ return access(join10(root, "project.json")).then(() => true, () => false);
6894
7361
  }
6895
7362
  async function orgsToSync() {
6896
7363
  const [stamps, stampedAccounts, accounts] = await Promise.all([
@@ -6965,23 +7432,23 @@ async function reconcileOrgCanary(orgId) {
6965
7432
  }
6966
7433
 
6967
7434
  // ../server/src/services/cloud-sync-bridge.ts
6968
- import { randomUUID as randomUUID7 } from "crypto";
6969
- import { mkdir as mkdir7, rm as rm3 } from "fs/promises";
7435
+ import { randomUUID as randomUUID8 } from "crypto";
7436
+ import { mkdir as mkdir8, rm as rm3 } from "fs/promises";
6970
7437
  import * as Y2 from "yjs";
6971
7438
 
6972
7439
  // ../server/src/services/sync-doc-cache.ts
6973
7440
  import { readFileSync as readFileSync2, renameSync, rmSync, writeFileSync, mkdirSync } from "fs";
6974
- import { readFile as readFile4 } from "fs/promises";
6975
- import { dirname as dirname5, join as join10 } from "path";
6976
- import { randomUUID as randomUUID6 } from "crypto";
7441
+ import { readFile as readFile6 } from "fs/promises";
7442
+ import { dirname as dirname6, join as join11 } from "path";
7443
+ import { randomUUID as randomUUID7 } from "crypto";
6977
7444
  import * as Y from "yjs";
6978
- var FILE = "sync-doc.bin";
7445
+ var FILE2 = "sync-doc.bin";
6979
7446
  function cachePath(orgId) {
6980
- return join10(orgVaultRoot(orgId), FILE);
7447
+ return join11(orgVaultRoot(orgId), FILE2);
6981
7448
  }
6982
7449
  async function readSyncDoc(orgId) {
6983
7450
  try {
6984
- return new Uint8Array(await readFile4(cachePath(orgId)));
7451
+ return new Uint8Array(await readFile6(cachePath(orgId)));
6985
7452
  } catch {
6986
7453
  return null;
6987
7454
  }
@@ -7004,8 +7471,8 @@ function writeSyncDocSync(orgId, state) {
7004
7471
  existing = new Uint8Array(readFileSync2(path));
7005
7472
  } catch {
7006
7473
  }
7007
- mkdirSync(dirname5(path), { recursive: true });
7008
- const tmp = `${path}.${process.pid}.${randomUUID6()}.tmp`;
7474
+ mkdirSync(dirname6(path), { recursive: true });
7475
+ const tmp = `${path}.${process.pid}.${randomUUID7()}.tmp`;
7009
7476
  try {
7010
7477
  writeFileSync(tmp, mergeWith(existing, state), { mode: 384 });
7011
7478
  renameSync(tmp, path);
@@ -7058,7 +7525,7 @@ var OrgSyncConnection = class {
7058
7525
  services = getCollection(this.doc, "services");
7059
7526
  environments = getCollection(this.doc, "environments");
7060
7527
  logParsers = getCollection(this.doc, PARSERS);
7061
- conn = randomUUID7();
7528
+ conn = randomUUID8();
7062
7529
  link;
7063
7530
  stopped = false;
7064
7531
  initializing = true;
@@ -7108,8 +7575,8 @@ var OrgSyncConnection = class {
7108
7575
  * Réglée quand le document tient l'état d'ouverture du cloud — ou quand il ne
7109
7576
  * l'aura pas : session refusée, app trop ancienne, connexion arrêtée.
7110
7577
  */
7111
- opened = new Promise((resolve4) => {
7112
- this.markOpened = resolve4;
7578
+ opened = new Promise((resolve5) => {
7579
+ this.markOpened = resolve5;
7113
7580
  });
7114
7581
  /** Store applications started by remote events and not finished yet. */
7115
7582
  applying = /* @__PURE__ */ new Set();
@@ -7491,7 +7958,7 @@ var OrgSyncConnection = class {
7491
7958
  }
7492
7959
  const isLocal = existing?.orgId === this.link.orgId;
7493
7960
  if (!inDoc && !isLocal) return false;
7494
- await mkdir7(projectVaultRoot(this.link.orgId, projectId), { recursive: true });
7961
+ await mkdir8(projectVaultRoot(this.link.orgId, projectId), { recursive: true });
7495
7962
  this.tracked.add(projectId);
7496
7963
  if (inDoc) await this.pullFromDoc([projectId]);
7497
7964
  else await this.reconcileLocalToDoc();
@@ -7970,8 +8437,8 @@ var CloudSyncBridge = class {
7970
8437
  let timer;
7971
8438
  await Promise.race([
7972
8439
  conn.opened,
7973
- new Promise((resolve4) => {
7974
- timer = setTimeout(resolve4, timeoutMs);
8440
+ new Promise((resolve5) => {
8441
+ timer = setTimeout(resolve5, timeoutMs);
7975
8442
  })
7976
8443
  ]);
7977
8444
  clearTimeout(timer);
@@ -7988,9 +8455,9 @@ var CloudSyncBridge = class {
7988
8455
  var cloudSyncBridge = new CloudSyncBridge();
7989
8456
 
7990
8457
  // ../server/src/services/key-adoption.ts
7991
- import { readdir as readdir3, readFile as readFile5, writeFile as writeFile6, mkdir as mkdir8, copyFile, rename as rename2, stat as stat5 } from "fs/promises";
7992
- import { join as join11 } from "path";
7993
- import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes2 } from "crypto";
8458
+ import { readdir as readdir3, readFile as readFile7, writeFile as writeFile6, mkdir as mkdir9, copyFile, rename as rename2, stat as stat5 } from "fs/promises";
8459
+ import { dirname as dirname7, join as join12 } from "path";
8460
+ import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes3 } from "crypto";
7994
8461
  var ALGORITHM = "aes-256-gcm";
7995
8462
  function isAgentTriplet(value) {
7996
8463
  const hex = (v) => typeof v === "string" && /^[0-9a-f]+$/i.test(v);
@@ -8002,7 +8469,7 @@ function decryptTriplet(key, t) {
8002
8469
  return decipher.update(t.encrypted, "hex", "utf-8") + decipher.final("utf-8");
8003
8470
  }
8004
8471
  function encryptTriplet(key, plaintext) {
8005
- const iv = randomBytes2(16);
8472
+ const iv = randomBytes3(16);
8006
8473
  const cipher = createCipheriv2(ALGORITHM, key, iv);
8007
8474
  const encrypted = cipher.update(plaintext, "utf-8", "hex") + cipher.final("hex");
8008
8475
  return { encrypted, iv: iv.toString("hex"), authTag: cipher.getAuthTag().toString("hex") };
@@ -8044,14 +8511,22 @@ async function rootsOfOrg(orgId) {
8044
8511
  }
8045
8512
  return Array.from(roots);
8046
8513
  }
8047
- async function jsonFilesIn(root) {
8514
+ var ENCRYPTED_SUBDIRS = [CONVERSATIONS_DIRNAME];
8515
+ async function jsonNamesIn(dir) {
8048
8516
  try {
8049
- const entries2 = await readdir3(root, { withFileTypes: true });
8517
+ const entries2 = await readdir3(dir, { withFileTypes: true });
8050
8518
  return entries2.filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => e.name);
8051
8519
  } catch {
8052
8520
  return [];
8053
8521
  }
8054
8522
  }
8523
+ async function jsonFilesIn(root) {
8524
+ const names = await jsonNamesIn(root);
8525
+ for (const sub of ENCRYPTED_SUBDIRS) {
8526
+ names.push(...(await jsonNamesIn(join12(root, sub))).map((n) => join12(sub, n)));
8527
+ }
8528
+ return names;
8529
+ }
8055
8530
  async function exists(path) {
8056
8531
  return stat5(path).then(() => true, () => false);
8057
8532
  }
@@ -8086,10 +8561,10 @@ async function adoptOrgKey(orgId, newKey, masterPassword, now) {
8086
8561
  const touchedRoots = /* @__PURE__ */ new Set();
8087
8562
  for (const vaultRoot of await rootsOfOrg(orgId)) {
8088
8563
  for (const name of await jsonFilesIn(vaultRoot)) {
8089
- const path = join11(vaultRoot, name);
8564
+ const path = join12(vaultRoot, name);
8090
8565
  let parsed;
8091
8566
  try {
8092
- parsed = JSON.parse(await readFile5(path, "utf-8"));
8567
+ parsed = JSON.parse(await readFile7(path, "utf-8"));
8093
8568
  } catch {
8094
8569
  continue;
8095
8570
  }
@@ -8103,14 +8578,15 @@ async function adoptOrgKey(orgId, newKey, masterPassword, now) {
8103
8578
  const stamp = now.toISOString().replace(/[:.]/g, "-").substring(0, 19);
8104
8579
  const backups = [];
8105
8580
  for (const vaultRoot of touchedRoots) {
8106
- const backupDir = join11(vaultRoot, `backup-key-${stamp}`);
8107
- await mkdir8(backupDir, { recursive: true });
8581
+ const backupDir = join12(vaultRoot, `backup-key-${stamp}`);
8582
+ await mkdir9(backupDir, { recursive: true });
8108
8583
  for (const name of await jsonFilesIn(vaultRoot)) {
8109
- await copyFile(join11(vaultRoot, name), join11(backupDir, name));
8584
+ await mkdir9(dirname7(join12(backupDir, name)), { recursive: true });
8585
+ await copyFile(join12(vaultRoot, name), join12(backupDir, name));
8110
8586
  }
8111
8587
  for (const name of [".encryption.key.enc", ".runeya-verify"]) {
8112
- if (await exists(join11(vaultRoot, name))) {
8113
- await copyFile(join11(vaultRoot, name), join11(backupDir, name));
8588
+ if (await exists(join12(vaultRoot, name))) {
8589
+ await copyFile(join12(vaultRoot, name), join12(backupDir, name));
8114
8590
  }
8115
8591
  }
8116
8592
  backups.push(backupDir);
@@ -8129,7 +8605,7 @@ async function adoptOrgKey(orgId, newKey, masterPassword, now) {
8129
8605
  return { converted: pending.map(({ path, values }) => ({ file: path, values })), backups };
8130
8606
  }
8131
8607
  async function resetOrgKeyToFresh(orgId, masterPassword, now) {
8132
- const freshKey = randomBytes2(32);
8608
+ const freshKey = randomBytes3(32);
8133
8609
  if (isVaultLocked(orgId) || !getEncryptionKey(orgId)) {
8134
8610
  await readEncryptedKey(await keyRoot(), masterPassword);
8135
8611
  await installOrgKey(orgId, freshKey, masterPassword);
@@ -8165,7 +8641,7 @@ async function assertOrgReadable(orgId) {
8165
8641
  await assertOrgVaultReadable(orgId);
8166
8642
  } catch (err) {
8167
8643
  if (err instanceof OrgVaultUnreadableError) {
8168
- throw new TRPCError20({ code: "FORBIDDEN", message: err.message });
8644
+ throw new TRPCError22({ code: "FORBIDDEN", message: err.message });
8169
8645
  }
8170
8646
  throw err;
8171
8647
  }
@@ -8175,10 +8651,10 @@ async function assertOrgKey(orgId) {
8175
8651
  await reconcileOrgCanary(orgId);
8176
8652
  } catch (err) {
8177
8653
  if (err instanceof OrgKeyMismatchError) {
8178
- throw new TRPCError20({ code: "FORBIDDEN", message: err.message });
8654
+ throw new TRPCError22({ code: "FORBIDDEN", message: err.message });
8179
8655
  }
8180
8656
  if (err instanceof CanaryUnavailableError) {
8181
- throw new TRPCError20({ code: "PRECONDITION_FAILED", message: err.message });
8657
+ throw new TRPCError22({ code: "PRECONDITION_FAILED", message: err.message });
8182
8658
  }
8183
8659
  throw err;
8184
8660
  }
@@ -8195,14 +8671,14 @@ var cloudSyncRouter = router({
8195
8671
  * "open this project" is the whole gesture.
8196
8672
  */
8197
8673
  trackProject: protectedProcedure.input(
8198
- z24.object({
8199
- orgId: z24.string().min(1).max(128),
8200
- cloudUrl: z24.string().url().max(2048),
8201
- projectId: z24.string().min(1).max(128),
8674
+ z27.object({
8675
+ orgId: z27.string().min(1).max(128),
8676
+ cloudUrl: z27.string().url().max(2048),
8677
+ projectId: z27.string().min(1).max(128),
8202
8678
  // Le compte sous lequel cette organisation a été listée. Tamponné avec
8203
8679
  // elle : c'est ce qui permettra à tout appel ultérieur de retrouver le
8204
8680
  // bon token sans qu'on ait à le lui redire.
8205
- accountId: z24.string().max(64).optional()
8681
+ accountId: z27.string().max(64).optional()
8206
8682
  })
8207
8683
  ).mutation(async ({ input }) => {
8208
8684
  await assertOrgReadable(input.orgId);
@@ -8224,7 +8700,7 @@ var cloudSyncRouter = router({
8224
8700
  return { pulled, state: syncState(input.orgId) };
8225
8701
  } catch (err) {
8226
8702
  if (err instanceof ProjectHeldElsewhereError) {
8227
- throw new TRPCError20({ code: "CONFLICT", message: err.code, cause: err });
8703
+ throw new TRPCError22({ code: "CONFLICT", message: err.code, cause: err });
8228
8704
  }
8229
8705
  throw err;
8230
8706
  }
@@ -8243,22 +8719,22 @@ var cloudSyncRouter = router({
8243
8719
  * fusionné.
8244
8720
  */
8245
8721
  publishProject: protectedProcedure.input(
8246
- z24.object({
8247
- orgId: z24.string().min(1).max(128),
8248
- cloudUrl: z24.string().url().max(2048),
8249
- projectId: z24.string().min(1).max(128),
8722
+ z27.object({
8723
+ orgId: z27.string().min(1).max(128),
8724
+ cloudUrl: z27.string().url().max(2048),
8725
+ projectId: z27.string().min(1).max(128),
8250
8726
  // Le compte sous lequel cette organisation a été listée. Tamponné avec
8251
8727
  // elle : c'est ce qui permettra à tout appel ultérieur de retrouver le
8252
8728
  // bon token sans qu'on ait à le lui redire.
8253
- accountId: z24.string().max(64).optional()
8729
+ accountId: z27.string().max(64).optional()
8254
8730
  })
8255
8731
  ).mutation(async ({ input }) => {
8256
8732
  const local = await projectStore.get({ id: input.projectId, orgId: null });
8257
8733
  if (!local) {
8258
- throw new TRPCError20({ code: "NOT_FOUND", message: "PROJECT_NOT_LOCAL" });
8734
+ throw new TRPCError22({ code: "NOT_FOUND", message: "PROJECT_NOT_LOCAL" });
8259
8735
  }
8260
8736
  if (await projectStore.get({ id: input.projectId, orgId: input.orgId })) {
8261
- throw new TRPCError20({ code: "CONFLICT", message: "PROJECT_ALREADY_IN_ORG" });
8737
+ throw new TRPCError22({ code: "CONFLICT", message: "PROJECT_ALREADY_IN_ORG" });
8262
8738
  }
8263
8739
  await assertOrgReadable(input.orgId);
8264
8740
  const connected = cloudSyncBridge.states().some((s) => s.orgId === input.orgId);
@@ -8283,7 +8759,7 @@ var cloudSyncRouter = router({
8283
8759
  await cloudSyncBridge.followProject(input.orgId, input.projectId);
8284
8760
  } catch (err) {
8285
8761
  if (err instanceof ProjectHeldElsewhereError) {
8286
- throw new TRPCError20({ code: "CONFLICT", message: err.code, cause: err });
8762
+ throw new TRPCError22({ code: "CONFLICT", message: err.code, cause: err });
8287
8763
  }
8288
8764
  throw err;
8289
8765
  }
@@ -8297,16 +8773,16 @@ var cloudSyncRouter = router({
8297
8773
  * publié. Le cloud, lui, ne peut pas répondre — il n'a que du chiffré sous
8298
8774
  * les yeux — donc le canari est rapatrié et jugé ici.
8299
8775
  */
8300
- checkOrgKey: protectedProcedure.input(z24.object({
8301
- orgId: z24.string().min(1).max(128),
8302
- keyHex: z24.string().length(64).regex(/^[0-9a-f]+$/i)
8776
+ checkOrgKey: protectedProcedure.input(z27.object({
8777
+ orgId: z27.string().min(1).max(128),
8778
+ keyHex: z27.string().length(64).regex(/^[0-9a-f]+$/i)
8303
8779
  })).query(async ({ input }) => {
8304
8780
  const res = await cloudFetch2(
8305
8781
  `/api/auth/organization/canary?organizationId=${encodeURIComponent(input.orgId)}`,
8306
8782
  { method: "GET", accountId: await accountForOrgScope(input.orgId) }
8307
8783
  );
8308
8784
  if (!res.ok) {
8309
- throw new TRPCError20({ code: "BAD_GATEWAY", message: await cloudMessage(res) || `CANARY_READ_FAILED (${res.status})` });
8785
+ throw new TRPCError22({ code: "BAD_GATEWAY", message: await cloudMessage(res) || `CANARY_READ_FAILED (${res.status})` });
8310
8786
  }
8311
8787
  const { canary } = await res.json();
8312
8788
  if (typeof canary !== "string") return { matches: false, published: false };
@@ -8321,7 +8797,7 @@ var cloudSyncRouter = router({
8321
8797
  * que l'interface puisse proposer ni l'adoption ni la remise à zéro — le seul
8322
8798
  * chemin qui la répare. Rien n'est écrit ici, ni sur le disque ni sur le cloud.
8323
8799
  */
8324
- orgKeyStatus: protectedProcedure.input(z24.object({ orgId: z24.string().min(1).max(128) })).query(async ({ input }) => {
8800
+ orgKeyStatus: protectedProcedure.input(z27.object({ orgId: z27.string().min(1).max(128) })).query(async ({ input }) => {
8325
8801
  const key = getEncryptionKey(input.orgId);
8326
8802
  if (!key) return { status: "unknown" };
8327
8803
  const res = await cloudFetch2(
@@ -8344,10 +8820,10 @@ var cloudSyncRouter = router({
8344
8820
  * Bornée au coffre de l'organisation et à ceux de ses projets : les autres
8345
8821
  * organisations et la machine gardent leur clé.
8346
8822
  */
8347
- adoptOrgKey: protectedProcedure.input(z24.object({
8348
- orgId: z24.string().min(1).max(128),
8349
- keyHex: z24.string().length(64).regex(/^[0-9a-f]+$/i),
8350
- masterPassword: z24.string().min(1).max(1024)
8823
+ adoptOrgKey: protectedProcedure.input(z27.object({
8824
+ orgId: z27.string().min(1).max(128),
8825
+ keyHex: z27.string().length(64).regex(/^[0-9a-f]+$/i),
8826
+ masterPassword: z27.string().min(1).max(1024)
8351
8827
  })).mutation(async ({ input }) => {
8352
8828
  const report = await adoptOrgKey(
8353
8829
  input.orgId,
@@ -8377,14 +8853,14 @@ var cloudSyncRouter = router({
8377
8853
  * Ce qui a déjà été synchronisé sous l'ancienne clé de référence reste
8378
8854
  * illisible : aucune clé d'ici ne l'ouvre. L'interface le dit avant.
8379
8855
  */
8380
- resetOrgKey: protectedProcedure.input(z24.object({
8381
- orgId: z24.string().min(1).max(128),
8382
- masterPassword: z24.string().min(1).max(1024)
8856
+ resetOrgKey: protectedProcedure.input(z27.object({
8857
+ orgId: z27.string().min(1).max(128),
8858
+ masterPassword: z27.string().min(1).max(1024)
8383
8859
  })).mutation(async ({ input }) => {
8384
8860
  const { keyHex } = await resetOrgKeyToFresh(input.orgId, input.masterPassword, /* @__PURE__ */ new Date());
8385
8861
  const canary = await readVerifyFile(orgVaultRoot(input.orgId));
8386
8862
  if (!canary) {
8387
- throw new TRPCError20({
8863
+ throw new TRPCError22({
8388
8864
  code: "PRECONDITION_FAILED",
8389
8865
  message: "Le coffre de cette organisation n'a pas re\xE7u sa cl\xE9."
8390
8866
  });
@@ -8396,7 +8872,7 @@ var cloudSyncRouter = router({
8396
8872
  });
8397
8873
  if (!res.ok) {
8398
8874
  const detail = await cloudMessage(res);
8399
- throw new TRPCError20({
8875
+ throw new TRPCError22({
8400
8876
  code: res.status === 403 ? "FORBIDDEN" : "BAD_GATEWAY",
8401
8877
  message: detail || `ORG_KEY_RESET_FAILED (${res.status})`
8402
8878
  });
@@ -8412,9 +8888,9 @@ var cloudSyncRouter = router({
8412
8888
  * every other member and for this one on another machine.
8413
8889
  */
8414
8890
  untrackProject: protectedProcedure.input(
8415
- z24.object({
8416
- orgId: z24.string().min(1).max(128),
8417
- projectId: z24.string().min(1).max(128)
8891
+ z27.object({
8892
+ orgId: z27.string().min(1).max(128),
8893
+ projectId: z27.string().min(1).max(128)
8418
8894
  })
8419
8895
  ).mutation(async ({ input }) => {
8420
8896
  await cloudSyncBridge.unfollowProject(input.orgId, input.projectId);
@@ -8430,9 +8906,9 @@ var cloudSyncRouter = router({
8430
8906
  * membre, ce qui évite d'effacer les fichiers si le cloud a refusé l'ordre.
8431
8907
  */
8432
8908
  deleteProjectFromOrg: protectedProcedure.input(
8433
- z24.object({
8434
- orgId: z24.string().min(1).max(128),
8435
- projectId: z24.string().min(1).max(128)
8909
+ z27.object({
8910
+ orgId: z27.string().min(1).max(128),
8911
+ projectId: z27.string().min(1).max(128)
8436
8912
  })
8437
8913
  ).mutation(async ({ input }) => {
8438
8914
  let res;
@@ -8445,8 +8921,8 @@ var cloudSyncRouter = router({
8445
8921
  accountId: await accountForOrgScope(input.orgId)
8446
8922
  });
8447
8923
  } catch (err) {
8448
- if (err instanceof TRPCError20 && err.code === "PRECONDITION_FAILED") {
8449
- throw new TRPCError20({ code: "PRECONDITION_FAILED", message: "CLOUD_NOT_LINKED" });
8924
+ if (err instanceof TRPCError22 && err.code === "PRECONDITION_FAILED") {
8925
+ throw new TRPCError22({ code: "PRECONDITION_FAILED", message: "CLOUD_NOT_LINKED" });
8450
8926
  }
8451
8927
  throw err;
8452
8928
  }
@@ -8454,12 +8930,12 @@ var cloudSyncRouter = router({
8454
8930
  const detail = await cloudMessage(res);
8455
8931
  if (res.status === 403) {
8456
8932
  const code = /not a member/i.test(detail) ? "PROJECT_NOT_MEMBER" : "PROJECT_DELETE_FORBIDDEN";
8457
- throw new TRPCError20({ code: "FORBIDDEN", message: code, cause: new Error(detail) });
8933
+ throw new TRPCError22({ code: "FORBIDDEN", message: code, cause: new Error(detail) });
8458
8934
  }
8459
8935
  if (res.status === 404) {
8460
- throw new TRPCError20({ code: "NOT_FOUND", message: "PROJECT_NOT_IN_ORG", cause: new Error(detail) });
8936
+ throw new TRPCError22({ code: "NOT_FOUND", message: "PROJECT_NOT_IN_ORG", cause: new Error(detail) });
8461
8937
  }
8462
- throw new TRPCError20({
8938
+ throw new TRPCError22({
8463
8939
  code: "BAD_GATEWAY",
8464
8940
  message: detail || `PROJECT_DELETE_FAILED (${res.status})`
8465
8941
  });
@@ -8488,10 +8964,10 @@ var cloudSyncRouter = router({
8488
8964
  * d'un autre. Même forme que `rememberOrgNames`, et pour la même raison :
8489
8965
  * ce que seul le cloud sait, on le recopie quand il le dit.
8490
8966
  */
8491
- rememberOrgAccounts: protectedProcedure.input(z24.object({
8492
- orgs: z24.array(z24.object({
8493
- orgId: z24.string().min(1).max(128),
8494
- accountId: z24.string().min(1).max(64)
8967
+ rememberOrgAccounts: protectedProcedure.input(z27.object({
8968
+ orgs: z27.array(z27.object({
8969
+ orgId: z27.string().min(1).max(128),
8970
+ accountId: z27.string().min(1).max(64)
8495
8971
  })).max(200)
8496
8972
  })).mutation(async ({ input }) => {
8497
8973
  const candidates = /* @__PURE__ */ new Map();
@@ -8518,10 +8994,10 @@ var cloudSyncRouter = router({
8518
8994
  }
8519
8995
  return { remembered };
8520
8996
  }),
8521
- rememberOrgNames: protectedProcedure.input(z24.object({
8522
- orgs: z24.array(z24.object({
8523
- orgId: z24.string().min(1).max(128),
8524
- name: z24.string().min(1).max(100)
8997
+ rememberOrgNames: protectedProcedure.input(z27.object({
8998
+ orgs: z27.array(z27.object({
8999
+ orgId: z27.string().min(1).max(128),
9000
+ name: z27.string().min(1).max(100)
8525
9001
  })).max(200)
8526
9002
  })).mutation(async ({ input }) => {
8527
9003
  for (const { orgId, name } of input.orgs) await rememberOrgName(orgId, name);
@@ -8551,7 +9027,7 @@ var cloudSyncRouter = router({
8551
9027
  * Un 404 efface le logo retenu — l'organisation en a retiré un — tandis qu'un
8552
9028
  * réseau muet laisse en place ce qui est déjà là : hors ligne, on garde.
8553
9029
  */
8554
- cacheOrgLogos: protectedProcedure.input(z24.object({ orgIds: z24.array(z24.string().min(1).max(128)).max(200) })).mutation(async ({ input }) => {
9030
+ cacheOrgLogos: protectedProcedure.input(z27.object({ orgIds: z27.array(z27.string().min(1).max(128)).max(200) })).mutation(async ({ input }) => {
8555
9031
  let cached = 0;
8556
9032
  for (const orgId of input.orgIds) {
8557
9033
  const base = await orgCloudUrl(orgId);
@@ -8608,22 +9084,22 @@ var cloudSyncRouter = router({
8608
9084
 
8609
9085
  // ../server/src/trpc/routers/filesystem.ts
8610
9086
  import { readdir as readdir4, stat as stat6 } from "fs/promises";
8611
- import { homedir as homedir3 } from "os";
8612
- import { isAbsolute as isAbsolute3, join as join12, resolve as resolve2, dirname as dirname6, basename } from "path";
8613
- import { z as z25 } from "zod";
9087
+ import { homedir as homedir4 } from "os";
9088
+ import { isAbsolute as isAbsolute3, join as join13, resolve as resolve3, dirname as dirname8, basename } from "path";
9089
+ import { z as z28 } from "zod";
8614
9090
  var MAX_ENTRIES2 = 500;
8615
- var PATH_SCHEMA = z25.string().min(1).max(4096);
9091
+ var PATH_SCHEMA = z28.string().min(1).max(4096);
8616
9092
  function safeResolve(input) {
8617
9093
  if (input.includes("\0")) throw new Error("Chemin invalide");
8618
- const expanded = input.startsWith("~") ? join12(homedir3(), input.slice(1)) : input;
9094
+ const expanded = input.startsWith("~") ? join13(homedir4(), input.slice(1)) : input;
8619
9095
  if (!isAbsolute3(expanded)) throw new Error("Le chemin doit \xEAtre absolu");
8620
- return resolve2(expanded);
9096
+ return resolve3(expanded);
8621
9097
  }
8622
9098
  var filesystemRouter = router({
8623
9099
  /** Where a picker should open when the user has chosen nothing yet. */
8624
9100
  suggestedRoots: protectedProcedure.query(async () => {
8625
9101
  const roots = [
8626
- { label: "home", path: homedir3() },
9102
+ { label: "home", path: homedir4() },
8627
9103
  // The launch directory, taken from the vault rather than the process.
8628
9104
  { label: "cwd", path: await resolveWorkspaceCwd({}) }
8629
9105
  ];
@@ -8636,7 +9112,7 @@ var filesystemRouter = router({
8636
9112
  * Hidden entries are skipped: they are noise for choosing a workspace, and
8637
9113
  * `.runeya` itself would invite placing a project inside another's data.
8638
9114
  */
8639
- listDirectories: protectedProcedure.input(z25.object({ path: PATH_SCHEMA })).query(async ({ input }) => {
9115
+ listDirectories: protectedProcedure.input(z28.object({ path: PATH_SCHEMA })).query(async ({ input }) => {
8640
9116
  const dir = safeResolve(input.path);
8641
9117
  const info = await stat6(dir).catch(() => null);
8642
9118
  if (!info?.isDirectory()) {
@@ -8651,13 +9127,13 @@ var filesystemRouter = router({
8651
9127
  const truncated = names.length > MAX_ENTRIES2;
8652
9128
  const entries2 = [];
8653
9129
  for (const name of names.slice(0, MAX_ENTRIES2)) {
8654
- const path = join12(dir, name);
9130
+ const path = join13(dir, name);
8655
9131
  entries2.push({ name, path, hasRuneyaProject: await holdsRuneyaProject(path) });
8656
9132
  }
8657
9133
  return {
8658
9134
  path: dir,
8659
9135
  // Null at the filesystem root, which is what stops "up" from looping.
8660
- parent: dirname6(dir) === dir ? null : dirname6(dir),
9136
+ parent: dirname8(dir) === dir ? null : dirname8(dir),
8661
9137
  name: basename(dir) || dir,
8662
9138
  entries: entries2,
8663
9139
  truncated,
@@ -8669,7 +9145,7 @@ var filesystemRouter = router({
8669
9145
  * qui s'y trouve, ou rien (déjà dans la liste, lié à un projet d'organisation,
8670
9146
  * racine machine).
8671
9147
  */
8672
- checkLocalProjectTarget: protectedProcedure.input(z25.object({ path: PATH_SCHEMA })).query(async ({ input }) => {
9148
+ checkLocalProjectTarget: protectedProcedure.input(z28.object({ path: PATH_SCHEMA })).query(async ({ input }) => {
8673
9149
  const dir = safeResolve(input.path);
8674
9150
  const info = await stat6(dir).catch(() => null);
8675
9151
  if (!info?.isDirectory()) return { path: dir, status: "missing", projectName: null };
@@ -8681,10 +9157,10 @@ var filesystemRouter = router({
8681
9157
  * Two projects in one directory is what makes services run against the wrong
8682
9158
  * code, so a directory that already holds one is refused outright.
8683
9159
  */
8684
- checkProjectTarget: protectedProcedure.input(z25.object({
9160
+ checkProjectTarget: protectedProcedure.input(z28.object({
8685
9161
  path: PATH_SCHEMA,
8686
- projectId: z25.string().min(1).max(128),
8687
- orgId: z25.string().max(128).nullable().optional()
9162
+ projectId: z28.string().min(1).max(128),
9163
+ orgId: z28.string().max(128).nullable().optional()
8688
9164
  })).query(async ({ input }) => {
8689
9165
  const dir = safeResolve(input.path);
8690
9166
  const info = await stat6(dir).catch(() => null);
@@ -8703,15 +9179,15 @@ var filesystemRouter = router({
8703
9179
  });
8704
9180
 
8705
9181
  // ../server/src/trpc/routers/desktop.ts
8706
- import { z as z26 } from "zod";
9182
+ import { z as z29 } from "zod";
8707
9183
  import { isAbsolute as isAbsolute5 } from "path";
8708
- import { TRPCError as TRPCError21 } from "@trpc/server";
9184
+ import { TRPCError as TRPCError23 } from "@trpc/server";
8709
9185
 
8710
9186
  // ../server/src/services/desktop-launcher.ts
8711
- import { execFile as execFile4 } from "child_process";
9187
+ import { execFile as execFile5 } from "child_process";
8712
9188
  import { isAbsolute as isAbsolute4 } from "path";
8713
9189
  import { promisify as promisify3 } from "util";
8714
- var exec = promisify3(execFile4);
9190
+ var exec = promisify3(execFile5);
8715
9191
  var INSTALL_TIMEOUT_MS2 = 3e4;
8716
9192
  var cli = null;
8717
9193
  function registerCli(location) {
@@ -8742,7 +9218,8 @@ async function installLauncher(input) {
8742
9218
  "--port",
8743
9219
  String(input.port),
8744
9220
  "--agent-port",
8745
- String(input.agentPort)
9221
+ String(input.agentPort),
9222
+ ...input.projectId ? ["--project", input.projectId] : []
8746
9223
  ];
8747
9224
  const { stdout, stderr } = await exec(cli.execPath, args, {
8748
9225
  timeout: INSTALL_TIMEOUT_MS2,
@@ -8788,43 +9265,45 @@ var desktopRouter = router({
8788
9265
  * dessinée par le serveur. Sans cette annonce elle garderait la teinte figée
8789
9266
  * du fichier livré avec le CLI. Rien n'est persisté — voir `tray-accent.ts`.
8790
9267
  */
8791
- setTrayAccent: protectedProcedure.input(z26.object({
9268
+ setTrayAccent: protectedProcedure.input(z29.object({
8792
9269
  // `#rgb` ou `#rrggbb` : la forme est vérifiée ici, pas rabotée plus loin.
8793
- color: z26.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/).nullable()
9270
+ color: z29.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/).nullable()
8794
9271
  })).mutation(({ input }) => {
8795
9272
  setTrayAccent(input.color);
8796
9273
  return { ok: true };
8797
9274
  }),
8798
- installLauncher: protectedProcedure.input(z26.object({
8799
- name: z26.string().trim().min(1).max(120),
9275
+ installLauncher: protectedProcedure.input(z29.object({
9276
+ name: z29.string().trim().min(1).max(120),
8800
9277
  /**
8801
9278
  * `null` : raccourci global, sans dossier épinglé. Sinon un chemin
8802
9279
  * absolu, refusé autrement : le dossier devient un argument positionnel
8803
9280
  * du CLI, où une valeur commençant par un tiret passerait pour un drapeau.
8804
9281
  */
8805
- workingDir: z26.string().max(4096).refine(isAbsolute5, {
9282
+ workingDir: z29.string().max(4096).refine(isAbsolute5, {
8806
9283
  message: "Le dossier de travail doit \xEAtre un chemin absolu."
8807
9284
  }).nullable(),
8808
- port: z26.number().int().min(1).max(65535),
8809
- agentPort: z26.number().int().min(1).max(65535)
9285
+ port: z29.number().int().min(1).max(65535),
9286
+ agentPort: z29.number().int().min(1).max(65535),
9287
+ /** Projet ouvert directement par le raccourci (`--project`). Absent : liste des projets. */
9288
+ projectId: z29.string().max(128).regex(LAUNCHER_PROJECT_ID_PATTERN).nullable().optional()
8810
9289
  })).mutation(async ({ input }) => {
8811
9290
  try {
8812
9291
  return await installLauncher(input);
8813
9292
  } catch (err) {
8814
9293
  const message = err.message;
8815
9294
  if (message === "launcher-working-dir-not-absolute") {
8816
- throw new TRPCError21({
9295
+ throw new TRPCError23({
8817
9296
  code: "BAD_REQUEST",
8818
9297
  message: "Le dossier de travail doit \xEAtre un chemin absolu."
8819
9298
  });
8820
9299
  }
8821
9300
  if (message === "launcher-unavailable") {
8822
- throw new TRPCError21({
9301
+ throw new TRPCError23({
8823
9302
  code: "PRECONDITION_FAILED",
8824
9303
  message: "Cette Runeya ne sait pas o\xF9 trouver son CLI : cr\xE9ez le raccourci avec `runeya install-desktop`."
8825
9304
  });
8826
9305
  }
8827
- throw new TRPCError21({ code: "INTERNAL_SERVER_ERROR", message });
9306
+ throw new TRPCError23({ code: "INTERNAL_SERVER_ERROR", message });
8828
9307
  }
8829
9308
  })
8830
9309
  });
@@ -8836,6 +9315,8 @@ var appRouter = router({
8836
9315
  appUpdate: appUpdateRouter,
8837
9316
  changelog: changelogRouter,
8838
9317
  diagnostics: diagnosticsRouter,
9318
+ mcpPairing: mcpPairingRouter,
9319
+ aiClients: aiClientsRouter,
8839
9320
  project: projectRouter,
8840
9321
  service: serviceRouter,
8841
9322
  agent: agentRouter,
@@ -8924,8 +9405,8 @@ async function ensureFilePermissions() {
8924
9405
  }
8925
9406
 
8926
9407
  // ../server/src/services/vault-migration.ts
8927
- 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";
8928
- import { join as join13 } from "path";
9408
+ 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";
9409
+ import { join as join14 } from "path";
8929
9410
  var KEY_FILES = [".encryption.key.enc", ".runeya-verify"];
8930
9411
  var MACHINE_FILES = [
8931
9412
  "agents.json",
@@ -8955,17 +9436,17 @@ async function openLegacyVault(legacyRoot, masterPassword) {
8955
9436
  }
8956
9437
  async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machineKey, now) {
8957
9438
  const stamp = now.toISOString().replace(/[:.]/g, "-").substring(0, 19);
8958
- const backupDir = join13(legacyRoot, `backup-${stamp}`);
8959
- await mkdir9(backupDir, { recursive: true });
9439
+ const backupDir = join14(legacyRoot, `backup-${stamp}`);
9440
+ await mkdir10(backupDir, { recursive: true });
8960
9441
  const jsonFiles = await vaultJsonFiles(legacyRoot);
8961
9442
  for (const name of [...jsonFiles, ...KEY_FILES]) {
8962
- if (await exists2(join13(legacyRoot, name))) {
8963
- await copyFile2(join13(legacyRoot, name), join13(backupDir, name));
9443
+ if (await exists2(join14(legacyRoot, name))) {
9444
+ await copyFile2(join14(legacyRoot, name), join14(backupDir, name));
8964
9445
  }
8965
9446
  }
8966
9447
  const rekeyed = [];
8967
9448
  for (const name of jsonFiles) {
8968
- const raw = await readFile6(join13(legacyRoot, name), "utf-8");
9449
+ const raw = await readFile8(join14(legacyRoot, name), "utf-8");
8969
9450
  let parsed;
8970
9451
  try {
8971
9452
  parsed = JSON.parse(raw);
@@ -8975,13 +9456,13 @@ async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machine
8975
9456
  const counter = { n: 0 };
8976
9457
  const converted = rekeyValue(parsed, legacyKey, machineKey, counter);
8977
9458
  if (counter.n === 0) continue;
8978
- const tmp = join13(legacyRoot, `${name}.tmp`);
9459
+ const tmp = join14(legacyRoot, `${name}.tmp`);
8979
9460
  await writeFile7(tmp, JSON.stringify(converted, null, 2), "utf-8");
8980
- await rename3(tmp, join13(legacyRoot, name));
9461
+ await rename3(tmp, join14(legacyRoot, name));
8981
9462
  rekeyed.push({ file: name, values: counter.n });
8982
9463
  }
8983
9464
  for (const { file, values } of rekeyed) {
8984
- const parsed = JSON.parse(await readFile6(join13(legacyRoot, file), "utf-8"));
9465
+ const parsed = JSON.parse(await readFile8(join14(legacyRoot, file), "utf-8"));
8985
9466
  const check = { n: 0 };
8986
9467
  rekeyValue(parsed, machineKey, machineKey, check);
8987
9468
  if (check.n !== values) {
@@ -8990,13 +9471,13 @@ async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machine
8990
9471
  );
8991
9472
  }
8992
9473
  }
8993
- await mkdir9(machineRootDir, { recursive: true });
9474
+ await mkdir10(machineRootDir, { recursive: true });
8994
9475
  const moved = [];
8995
9476
  const skipped = [];
8996
9477
  for (const name of MACHINE_FILES) {
8997
- const from = join13(legacyRoot, name);
9478
+ const from = join14(legacyRoot, name);
8998
9479
  if (!await exists2(from)) continue;
8999
- const to = join13(machineRootDir, name);
9480
+ const to = join14(machineRootDir, name);
9000
9481
  if (await exists2(to)) {
9001
9482
  skipped.push(name);
9002
9483
  continue;
@@ -9006,15 +9487,15 @@ async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machine
9006
9487
  moved.push(name);
9007
9488
  }
9008
9489
  for (const name of KEY_FILES) {
9009
- const path = join13(legacyRoot, name);
9490
+ const path = join14(legacyRoot, name);
9010
9491
  if (await exists2(path)) await unlink(path);
9011
9492
  }
9012
9493
  return { backupDir, rekeyed, moved, skipped };
9013
9494
  }
9014
9495
 
9015
9496
  // ../server/src/services/settings-migration.ts
9016
- 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";
9017
- import { join as join14 } from "path";
9497
+ 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";
9498
+ import { join as join15 } from "path";
9018
9499
  var SETTINGS_FILE = "settings.json";
9019
9500
  var SETTINGS_PRIVATE_FILE = "settings.private.json";
9020
9501
  async function exists3(path) {
@@ -9027,9 +9508,9 @@ async function legacySource(filename) {
9027
9508
  return null;
9028
9509
  }
9029
9510
  async function moveInto(root, filename, from) {
9030
- await mkdir10(root, { recursive: true });
9031
- const to = join14(root, filename);
9032
- const contents = await readFile7(from, "utf-8");
9511
+ await mkdir11(root, { recursive: true });
9512
+ const to = join15(root, filename);
9513
+ const contents = await readFile9(from, "utf-8");
9033
9514
  const tmp = to + ".tmp";
9034
9515
  await writeFile8(tmp, contents, "utf-8");
9035
9516
  await rename4(tmp, to);
@@ -9053,7 +9534,7 @@ async function migrateSettingsIntoVaults() {
9053
9534
  const orgId = orgs[0];
9054
9535
  const root = orgVaultRoot(orgId);
9055
9536
  for (const { filename, from } of sources) {
9056
- if (await exists3(join14(root, filename))) continue;
9537
+ if (await exists3(join15(root, filename))) continue;
9057
9538
  await moveInto(root, filename, from);
9058
9539
  report.files.push(filename);
9059
9540
  }
@@ -9066,17 +9547,17 @@ async function migrateSettingsIntoVaults() {
9066
9547
 
9067
9548
  // ../server/src/migrations/migrate-from-legacy.ts
9068
9549
  import { createRequire } from "module";
9069
- import { randomBytes as randomBytes3, createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3 } from "crypto";
9070
- import { randomUUID as randomUUID8 } from "crypto";
9550
+ import { randomBytes as randomBytes4, createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3 } from "crypto";
9551
+ import { randomUUID as randomUUID9 } from "crypto";
9071
9552
  import {
9072
- readFile as readFile8,
9553
+ readFile as readFile10,
9073
9554
  writeFile as writeFile9,
9074
9555
  rename as rename5,
9075
- mkdir as mkdir11,
9556
+ mkdir as mkdir12,
9076
9557
  access as access2,
9077
9558
  readdir as readdir6
9078
9559
  } from "fs/promises";
9079
- import { join as join15, dirname as dirname7 } from "path";
9560
+ import { join as join16, dirname as dirname9 } from "path";
9080
9561
  var _require = createRequire(import.meta.url);
9081
9562
  var PARSER_ID_MAP = {
9082
9563
  "stack-monitor-parser-jsons": "native:json",
@@ -9089,7 +9570,7 @@ var PARSER_ID_MAP = {
9089
9570
  function encryptValue2(plaintext) {
9090
9571
  const key = getEncryptionKey();
9091
9572
  if (!key) throw new Error("[legacy-migration] Encryption key not loaded");
9092
- const iv = randomBytes3(16);
9573
+ const iv = randomBytes4(16);
9093
9574
  const cipher = createCipheriv3("aes-256-gcm", key, iv);
9094
9575
  let enc = cipher.update(plaintext, "utf-8", "hex");
9095
9576
  enc += cipher.final("hex");
@@ -9193,7 +9674,7 @@ async function getSodium() {
9193
9674
  return _sodium;
9194
9675
  }
9195
9676
  async function decryptLegacyFile(filePath, key, sodium) {
9196
- const raw = (await readFile8(filePath, "utf-8")).trim();
9677
+ const raw = (await readFile10(filePath, "utf-8")).trim();
9197
9678
  if (!raw || raw === "[]" || raw === "{}") {
9198
9679
  try {
9199
9680
  return JSON.parse(raw);
@@ -9218,7 +9699,7 @@ async function atomicWrite(path, data) {
9218
9699
  }
9219
9700
  async function runLegacyMigration(dataDir) {
9220
9701
  const legacyRoot = env.DATA_DIR;
9221
- const legacyKeyPath = join15(legacyRoot, "dbs", "encryption-key.json");
9702
+ const legacyKeyPath = join16(legacyRoot, "dbs", "encryption-key.json");
9222
9703
  try {
9223
9704
  await access2(legacyKeyPath);
9224
9705
  } catch {
@@ -9228,18 +9709,18 @@ async function runLegacyMigration(dataDir) {
9228
9709
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
9229
9710
  const legacyTmp = `${legacyRoot}-tmp`;
9230
9711
  await rename5(legacyRoot, legacyTmp);
9231
- await mkdir11(dataDir, { recursive: true });
9232
- const legacyOld = join15(dataDir, `backup-${timestamp}`);
9712
+ await mkdir12(dataDir, { recursive: true });
9713
+ const legacyOld = join16(dataDir, `backup-${timestamp}`);
9233
9714
  await rename5(legacyTmp, legacyOld);
9234
9715
  console.log(`[legacy-migration] Legacy data backed up to ${legacyOld}`);
9235
- const keyJson = JSON.parse(await readFile8(join15(legacyOld, "dbs", "encryption-key.json"), "utf-8"));
9716
+ const keyJson = JSON.parse(await readFile10(join16(legacyOld, "dbs", "encryption-key.json"), "utf-8"));
9236
9717
  const sodium = await getSodium();
9237
9718
  const sodiumKey = sodium.from_base64(keyJson.encryptionKey);
9238
9719
  const keyBuffer = Buffer.from(sodiumKey);
9239
9720
  const runyeaKeyHex = keyBuffer.toString("hex");
9240
9721
  await setEncryptionKey(runyeaKeyHex);
9241
9722
  console.log("[legacy-migration] Encryption key loaded in memory (temporary)");
9242
- const dbsDir = join15(legacyOld, "dbs");
9723
+ const dbsDir = join16(legacyOld, "dbs");
9243
9724
  async function readEncrypted(filePath) {
9244
9725
  try {
9245
9726
  return await decryptLegacyFile(filePath, sodiumKey, sodium);
@@ -9248,29 +9729,29 @@ async function runLegacyMigration(dataDir) {
9248
9729
  return null;
9249
9730
  }
9250
9731
  }
9251
- const envsDir = join15(dbsDir, "envs");
9732
+ const envsDir = join16(dbsDir, "envs");
9252
9733
  const envFiles = (await readdir6(envsDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
9253
9734
  const legacyEnvs = {};
9254
9735
  for (const file of envFiles) {
9255
9736
  const name = file.replace(".encrypted.json", "");
9256
- legacyEnvs[name] = await readEncrypted(join15(envsDir, file));
9737
+ legacyEnvs[name] = await readEncrypted(join16(envsDir, file));
9257
9738
  }
9258
- const servicesDir = join15(dbsDir, "services");
9739
+ const servicesDir = join16(dbsDir, "services");
9259
9740
  const serviceFiles = (await readdir6(servicesDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
9260
9741
  const legacyServices = {};
9261
9742
  for (const file of serviceFiles) {
9262
9743
  const name = file.replace(".encrypted.json", "");
9263
9744
  if (!name) continue;
9264
- const data = await readEncrypted(join15(servicesDir, file));
9745
+ const data = await readEncrypted(join16(servicesDir, file));
9265
9746
  if (data?.label) legacyServices[name] = data;
9266
9747
  }
9267
- const overridesDir = join15(dbsDir, "overrides");
9748
+ const overridesDir = join16(dbsDir, "overrides");
9268
9749
  const overrideFiles = (await readdir6(overridesDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
9269
9750
  const legacyServiceOverrides = {};
9270
9751
  const legacyEnvOverrides = {};
9271
9752
  for (const file of overrideFiles) {
9272
9753
  const baseName = file.replace(".encrypted.json", "");
9273
- const data = await readEncrypted(join15(overridesDir, file));
9754
+ const data = await readEncrypted(join16(overridesDir, file));
9274
9755
  if (!data) continue;
9275
9756
  if (baseName.endsWith("-envs")) {
9276
9757
  legacyServiceOverrides[baseName.slice(0, -5)] = data;
@@ -9278,18 +9759,18 @@ async function runLegacyMigration(dataDir) {
9278
9759
  legacyEnvOverrides[baseName.slice(0, -12)] = data;
9279
9760
  }
9280
9761
  }
9281
- const parsersDir = join15(dbsDir, "parsers");
9762
+ const parsersDir = join16(dbsDir, "parsers");
9282
9763
  const parserFiles = (await readdir6(parsersDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
9283
9764
  const legacyParsers = [];
9284
9765
  for (const file of parserFiles) {
9285
- const data = await readEncrypted(join15(parsersDir, file));
9766
+ const data = await readEncrypted(join16(parsersDir, file));
9286
9767
  if (data?.id) legacyParsers.push(data);
9287
9768
  }
9288
- const projectMonoId = randomUUID8();
9769
+ const projectMonoId = randomUUID9();
9289
9770
  const envIdMap = {};
9290
9771
  const labelToKey = {};
9291
9772
  for (const [name, legacyEnv] of Object.entries(legacyEnvs)) {
9292
- envIdMap[name] = randomUUID8();
9773
+ envIdMap[name] = randomUUID9();
9293
9774
  if (legacyEnv?.label) labelToKey[legacyEnv.label] = name;
9294
9775
  }
9295
9776
  const runeEnvs = [];
@@ -9314,7 +9795,7 @@ async function runLegacyMigration(dataDir) {
9314
9795
  if (hasInvalidInterpolation(value)) continue;
9315
9796
  const cleaned = cleanVarKey(key);
9316
9797
  if (inheritedVars[cleaned] === value) continue;
9317
- const varId = randomUUID8();
9798
+ const varId = randomUUID9();
9318
9799
  variables[varId] = makeVariable(cleaned, value);
9319
9800
  varIdLookup.set(`${envIdMap[name]}:${cleaned}`, varId);
9320
9801
  }
@@ -9335,7 +9816,7 @@ async function runLegacyMigration(dataDir) {
9335
9816
  const serviceIdMap = {};
9336
9817
  const runeServices = [];
9337
9818
  for (const [name, legacy] of Object.entries(legacyServices)) {
9338
- const id = randomUUID8();
9819
+ const id = randomUUID9();
9339
9820
  serviceIdMap[name] = id;
9340
9821
  const rootPath = legacy.rootPath || "";
9341
9822
  const rootPathCwd = rootPath && rootPath !== "." ? rootPath : "";
@@ -9344,7 +9825,7 @@ async function runLegacyMigration(dataDir) {
9344
9825
  const parts = [cmd.spawnCmd, ...cmd.spawnArgs || []].filter(Boolean);
9345
9826
  if (!parts.length) continue;
9346
9827
  commands.push({
9347
- id: cmd.id || randomUUID8(),
9828
+ id: cmd.id || randomUUID9(),
9348
9829
  label: "Launch",
9349
9830
  command: parts.join(" "),
9350
9831
  cwd: cmd.spawnOptions?.cwd || "",
@@ -9405,7 +9886,7 @@ async function runLegacyMigration(dataDir) {
9405
9886
  const legacyBootstrapCmds = container.bootstrap?.commands || [];
9406
9887
  if (legacyBootstrapCmds.length > 0) {
9407
9888
  dockerConfig.bootstrapCommands = legacyBootstrapCmds.filter((bs) => bs.cmd || bs.entrypoint).map((bs, idx) => ({
9408
- id: bs.id || randomUUID8(),
9889
+ id: bs.id || randomUUID9(),
9409
9890
  label: bs.label || `Bootstrap ${idx + 1}`,
9410
9891
  command: bs.cmd || "",
9411
9892
  ...bs.entrypoint ? { entrypoint: bs.entrypoint } : {},
@@ -9453,7 +9934,7 @@ async function runLegacyMigration(dataDir) {
9453
9934
  cwd: serviceCwd,
9454
9935
  runner: dockerConfig ? "docker" : "native",
9455
9936
  shortcuts: (legacy.shortcuts || []).filter((s) => s.spawnCmd || s.label).map((s) => ({
9456
- id: s.id || randomUUID8(),
9937
+ id: s.id || randomUUID9(),
9457
9938
  label: s.label || s.spawnCmd || "",
9458
9939
  command: [s.spawnCmd, ...s.spawnArgs || []].filter(Boolean).join(" "),
9459
9940
  ...s.spawnOptions?.cwd ? { cwd: s.spawnOptions.cwd } : {}
@@ -9493,7 +9974,7 @@ async function runLegacyMigration(dataDir) {
9493
9974
  );
9494
9975
  if (!svcScopedEnv) {
9495
9976
  svcScopedEnv = {
9496
- id: randomUUID8(),
9977
+ id: randomUUID9(),
9497
9978
  projectId: projectMonoId,
9498
9979
  name: `${service?.name || name} (${globalEnv.name})`,
9499
9980
  scope: "service",
@@ -9517,7 +9998,7 @@ async function runLegacyMigration(dataDir) {
9517
9998
  const varPost = typeof varData === "object" ? String(varData.suffix ?? "") : "";
9518
9999
  if (!varKey || existingKeys.has(cleanVarKey(varKey))) continue;
9519
10000
  const cleaned = cleanVarKey(varKey);
9520
- const varId = randomUUID8();
10001
+ const varId = randomUUID9();
9521
10002
  svcScopedEnv.variables[varId] = makeVariable(cleaned, varValue, varPre, varPost);
9522
10003
  existingKeys.add(cleaned);
9523
10004
  }
@@ -9537,7 +10018,7 @@ async function runLegacyMigration(dataDir) {
9537
10018
  );
9538
10019
  if (!svcScopedEnv) {
9539
10020
  svcScopedEnv = {
9540
- id: randomUUID8(),
10021
+ id: randomUUID9(),
9541
10022
  projectId: projectMonoId,
9542
10023
  name: `${service?.name || serviceName} (${globalEnv.name})`,
9543
10024
  scope: "service",
@@ -9558,7 +10039,7 @@ async function runLegacyMigration(dataDir) {
9558
10039
  const baseEntry = Object.entries(svcScopedEnv.variables).find(([, v]) => v.key === cleaned);
9559
10040
  let varId = baseEntry?.[0];
9560
10041
  if (!varId) {
9561
- varId = randomUUID8();
10042
+ varId = randomUUID9();
9562
10043
  svcScopedEnv.variables[varId] = {
9563
10044
  key: cleaned,
9564
10045
  value: { value: "", pre: "", post: "", isSecret: false, shareWithAi: false, injectIntoAi: false, required: false, overrideOnly: false, description: "" }
@@ -9588,13 +10069,13 @@ async function runLegacyMigration(dataDir) {
9588
10069
  flatOverrides[varId] = makeVariable(cleaned, value);
9589
10070
  }
9590
10071
  } else {
9591
- flatOverrides[randomUUID8()] = makeVariable(cleaned, value);
10072
+ flatOverrides[randomUUID9()] = makeVariable(cleaned, value);
9592
10073
  }
9593
10074
  } else {
9594
10075
  const cleaned = cleanVarKey(key);
9595
10076
  const existingVarId = Object.entries(env2.variables).find(([, v]) => v.key === cleaned)?.[0];
9596
10077
  if (!existingVarId) {
9597
- const varId = randomUUID8();
10078
+ const varId = randomUUID9();
9598
10079
  env2.variables[varId] = makeVariable(cleaned, value);
9599
10080
  varIdLookup.set(`${envId}:${cleaned}`, varId);
9600
10081
  }
@@ -9623,7 +10104,7 @@ async function runLegacyMigration(dataDir) {
9623
10104
  }
9624
10105
  let existingSettings = {};
9625
10106
  try {
9626
- existingSettings = JSON.parse(await readFile8(join15(dataDir, "settings.json"), "utf-8"));
10107
+ existingSettings = JSON.parse(await readFile10(join16(dataDir, "settings.json"), "utf-8"));
9627
10108
  } catch {
9628
10109
  }
9629
10110
  const migratedParsers = legacyParsers.map((p) => ({
@@ -9644,11 +10125,11 @@ ${p.transform ?? ""}`,
9644
10125
  ...migratedParsers
9645
10126
  ]
9646
10127
  };
9647
- await atomicWrite(join15(dataDir, "projects.json"), runeProjects);
9648
- await atomicWrite(join15(dataDir, "services.json"), runeServices);
9649
- await atomicWrite(join15(dataDir, "environments.json"), runeEnvs);
9650
- await atomicWrite(join15(dataDir, "environment-overrides.json"), flatOverrides);
9651
- await atomicWrite(join15(dataDir, "settings.json"), newSettings);
10128
+ await atomicWrite(join16(dataDir, "projects.json"), runeProjects);
10129
+ await atomicWrite(join16(dataDir, "services.json"), runeServices);
10130
+ await atomicWrite(join16(dataDir, "environments.json"), runeEnvs);
10131
+ await atomicWrite(join16(dataDir, "environment-overrides.json"), flatOverrides);
10132
+ await atomicWrite(join16(dataDir, "settings.json"), newSettings);
9652
10133
  const svcEnvCount = runeEnvs.filter((e) => e.scope === "service").length;
9653
10134
  console.log(`[legacy-migration] Written: ${runeProjects.length} projects, ${runeServices.length} services, ${runeEnvs.length} environments (${svcEnvCount} service-scoped)`);
9654
10135
  console.log(`[legacy-migration] Overrides: ${Object.keys(flatOverrides).length}, custom parsers: ${migratedParsers.length} (all disabled \u2014 rewrite required)`);
@@ -9662,9 +10143,9 @@ async function findLegacyKeyInBackup(dataDir) {
9662
10143
  const entries2 = await readdir6(dataDir);
9663
10144
  const backupDirs = entries2.filter((e) => e.startsWith("backup-")).sort((a, b) => a < b ? -1 : a > b ? 1 : 0).reverse();
9664
10145
  for (const dir of backupDirs) {
9665
- const keyJsonPath = join15(dataDir, dir, "dbs", "encryption-key.json");
10146
+ const keyJsonPath = join16(dataDir, dir, "dbs", "encryption-key.json");
9666
10147
  try {
9667
- const raw = await readFile8(keyJsonPath, "utf-8");
10148
+ const raw = await readFile10(keyJsonPath, "utf-8");
9668
10149
  const parsed = JSON.parse(raw);
9669
10150
  if (!parsed.encryptionKey) continue;
9670
10151
  const sodium = await getSodium();
@@ -9682,11 +10163,11 @@ async function findLegacyKeyInBackup(dataDir) {
9682
10163
  return null;
9683
10164
  }
9684
10165
  async function runOverrideOnlyMigration(dataDir) {
9685
- const { resolve: resolve4 } = await import("path");
9686
- const abDataDir = resolve4(dataDir);
10166
+ const { resolve: resolve5 } = await import("path");
10167
+ const abDataDir = resolve5(dataDir);
9687
10168
  const candidateDirs = [
9688
- join15(dirname7(env.DATA_DIR), ".runeya", "dbs", "overrides"),
9689
- join15(abDataDir, "dbs", "overrides")
10169
+ join16(dirname9(env.DATA_DIR), ".runeya", "dbs", "overrides"),
10170
+ join16(abDataDir, "dbs", "overrides")
9690
10171
  ];
9691
10172
  let overridesDir = null;
9692
10173
  let overrideFiles = [];
@@ -9702,7 +10183,7 @@ async function runOverrideOnlyMigration(dataDir) {
9702
10183
  }
9703
10184
  }
9704
10185
  if (!overridesDir || overrideFiles.length === 0) return false;
9705
- const legacyKeyPath = join15(overridesDir, "..", "encryption-key.json");
10186
+ const legacyKeyPath = join16(overridesDir, "..", "encryption-key.json");
9706
10187
  try {
9707
10188
  await access2(legacyKeyPath);
9708
10189
  return false;
@@ -9720,7 +10201,7 @@ async function runOverrideOnlyMigration(dataDir) {
9720
10201
  const envNameToId = {};
9721
10202
  let envsRaw;
9722
10203
  try {
9723
- envsRaw = JSON.parse(await readFile8(join15(abDataDir, "environments.json"), "utf-8"));
10204
+ envsRaw = JSON.parse(await readFile10(join16(abDataDir, "environments.json"), "utf-8"));
9724
10205
  for (const _env of envsRaw) {
9725
10206
  const env2 = _env;
9726
10207
  if (env2["name"] && env2["id"]) {
@@ -9739,7 +10220,7 @@ async function runOverrideOnlyMigration(dataDir) {
9739
10220
  let servicesRaw = [];
9740
10221
  const serviceNameToId = {};
9741
10222
  try {
9742
- servicesRaw = JSON.parse(await readFile8(join15(abDataDir, "services.json"), "utf-8"));
10223
+ servicesRaw = JSON.parse(await readFile10(join16(abDataDir, "services.json"), "utf-8"));
9743
10224
  for (const _svc of servicesRaw) {
9744
10225
  const svc = _svc;
9745
10226
  if (svc["name"] && svc["id"]) serviceNameToId[svc["name"]] = svc["id"];
@@ -9756,7 +10237,7 @@ async function runOverrideOnlyMigration(dataDir) {
9756
10237
  const envName = baseName.slice(0, -"-environment".length);
9757
10238
  let data;
9758
10239
  try {
9759
- data = await decryptLegacyFile(join15(overridesDir, file), sodiumKey, sodium);
10240
+ data = await decryptLegacyFile(join16(overridesDir, file), sodiumKey, sodium);
9760
10241
  } catch (err) {
9761
10242
  console.warn(`[override-migration] Failed to decrypt ${file}: ${err.message}`);
9762
10243
  continue;
@@ -9778,12 +10259,12 @@ async function runOverrideOnlyMigration(dataDir) {
9778
10259
  if (isSameAsBase(value, baseVar)) continue;
9779
10260
  flatOverrides[varId] = makeVariable(cleaned, value);
9780
10261
  } else {
9781
- flatOverrides[randomUUID8()] = makeVariable(cleaned, value);
10262
+ flatOverrides[randomUUID9()] = makeVariable(cleaned, value);
9782
10263
  }
9783
10264
  }
9784
10265
  }
9785
10266
  if (Object.keys(flatOverrides).length > 0) {
9786
- await atomicWrite(join15(abDataDir, "environment-overrides.json"), flatOverrides);
10267
+ await atomicWrite(join16(abDataDir, "environment-overrides.json"), flatOverrides);
9787
10268
  console.log(`[override-migration] Written ${Object.keys(flatOverrides).length} override(s) to environment-overrides.json`);
9788
10269
  }
9789
10270
  let envsModified = false;
@@ -9793,7 +10274,7 @@ async function runOverrideOnlyMigration(dataDir) {
9793
10274
  const serviceName = baseName.slice(0, -"-envs".length);
9794
10275
  let data;
9795
10276
  try {
9796
- data = await decryptLegacyFile(join15(overridesDir, file), sodiumKey, sodium);
10277
+ data = await decryptLegacyFile(join16(overridesDir, file), sodiumKey, sodium);
9797
10278
  } catch (err) {
9798
10279
  console.warn(`[override-migration] Failed to decrypt ${file}: ${err.message}`);
9799
10280
  continue;
@@ -9815,7 +10296,7 @@ async function runOverrideOnlyMigration(dataDir) {
9815
10296
  );
9816
10297
  if (!svcScopedEnv) {
9817
10298
  svcScopedEnv = {
9818
- id: randomUUID8(),
10299
+ id: randomUUID9(),
9819
10300
  projectId: globalEnv["projectId"],
9820
10301
  name: `${service?.["name"] || serviceName} (${globalEnv["name"]})`,
9821
10302
  scope: "service",
@@ -9837,7 +10318,7 @@ async function runOverrideOnlyMigration(dataDir) {
9837
10318
  const baseEntry = Object.entries(svcScopedEnv["variables"]).find(([, v]) => v.key === cleaned);
9838
10319
  let varId = baseEntry?.[0];
9839
10320
  if (!varId) {
9840
- varId = randomUUID8();
10321
+ varId = randomUUID9();
9841
10322
  svcScopedEnv["variables"][varId] = {
9842
10323
  key: cleaned,
9843
10324
  value: { value: "", pre: "", post: "", isSecret: false, shareWithAi: false, injectIntoAi: false, required: false, overrideOnly: false, description: "" }
@@ -9852,7 +10333,7 @@ async function runOverrideOnlyMigration(dataDir) {
9852
10333
  }
9853
10334
  }
9854
10335
  if (envsModified) {
9855
- await atomicWrite(join15(abDataDir, "environments.json"), envsRaw);
10336
+ await atomicWrite(join16(abDataDir, "environments.json"), envsRaw);
9856
10337
  console.log("[override-migration] Merged service overrides into environments.json");
9857
10338
  }
9858
10339
  const migratedDir = overridesDir.replace(/overrides$/, "overrides-migrated");
@@ -9867,7 +10348,7 @@ async function runOverrideOnlyMigration(dataDir) {
9867
10348
  }
9868
10349
 
9869
10350
  // ../server/src/services/server-state.ts
9870
- import { join as join16 } from "path";
10351
+ import { join as join17 } from "path";
9871
10352
  import { stat as stat10 } from "fs/promises";
9872
10353
  var ENCRYPTED_KEY_FILENAME2 = ".encryption.key.enc";
9873
10354
  var VERIFY_FILENAME2 = ".runeya-verify";
@@ -9877,22 +10358,22 @@ async function getServerState() {
9877
10358
  let hasVerify = false;
9878
10359
  let hasData = false;
9879
10360
  try {
9880
- await stat10(join16(dataDir, ENCRYPTED_KEY_FILENAME2));
10361
+ await stat10(join17(dataDir, ENCRYPTED_KEY_FILENAME2));
9881
10362
  hasEncFile = true;
9882
10363
  } catch {
9883
10364
  }
9884
10365
  try {
9885
- await stat10(join16(dataDir, VERIFY_FILENAME2));
10366
+ await stat10(join17(dataDir, VERIFY_FILENAME2));
9886
10367
  hasVerify = true;
9887
10368
  } catch {
9888
10369
  }
9889
10370
  try {
9890
- await stat10(join16(dataDir, "projects.json"));
10371
+ await stat10(join17(dataDir, "projects.json"));
9891
10372
  hasData = true;
9892
10373
  } catch {
9893
10374
  }
9894
10375
  try {
9895
- await stat10(join16(dataDir, "project.json"));
10376
+ await stat10(join17(dataDir, "project.json"));
9896
10377
  hasData = true;
9897
10378
  } catch {
9898
10379
  }
@@ -10198,7 +10679,7 @@ function getOpenApiDocument() {
10198
10679
 
10199
10680
  // ../server/src/create-server.ts
10200
10681
  import rateLimit from "express-rate-limit";
10201
- import { existsSync as existsSync2, createReadStream } from "fs";
10682
+ import { existsSync as existsSync3, createReadStream } from "fs";
10202
10683
  import multer from "multer";
10203
10684
 
10204
10685
  // ../server/src/services/image-cleanup.service.ts
@@ -10295,6 +10776,7 @@ var llmsLimiter = rateLimit({
10295
10776
  async function createLocalServer() {
10296
10777
  installLogCapture();
10297
10778
  generateJwtSecret();
10779
+ setMcpClientCheck((clientId) => mcpPairing.isPaired(clientId));
10298
10780
  const ciMode = await loadFromEnv();
10299
10781
  await runLegacyMigration(env.DATA_DIR);
10300
10782
  const keyDir = await keyRoot();
@@ -10313,10 +10795,10 @@ async function createLocalServer() {
10313
10795
  console.log("[server] Premier lancement \u2014 setup requis");
10314
10796
  }
10315
10797
  }
10316
- const absoluteDataDir = resolve3(env.DATA_DIR);
10798
+ const absoluteDataDir = resolve4(env.DATA_DIR);
10317
10799
  console.log(`[server] Local project directory: ${absoluteDataDir}`);
10318
10800
  console.log(`[server] Machine directory: ${machineRoot()}`);
10319
- if (existsSync2(env.DATA_DIR)) {
10801
+ if (existsSync3(env.DATA_DIR)) {
10320
10802
  await ensureDataGitignore(env.DATA_DIR);
10321
10803
  }
10322
10804
  await settingsManager.getSettings();
@@ -10394,9 +10876,18 @@ async function createLocalServer() {
10394
10876
  await serviceStore.list().catch((err) => {
10395
10877
  console.warn("[server] Failed to preload services:", err);
10396
10878
  });
10879
+ void conversationStore.list().catch((err) => {
10880
+ console.warn("[server] Failed to preload conversations:", err);
10881
+ });
10397
10882
  void (async () => {
10398
10883
  const orgRoots = (await listOrgVaults()).map((orgId) => orgVaultRoot(orgId));
10399
- const removed = await sweepStaleTempFiles([machineRoot(), ...orgRoots, ...await allVaultRoots()]);
10884
+ const vaultRoots = await allVaultRoots();
10885
+ const removed = await sweepStaleTempFiles([
10886
+ machineRoot(),
10887
+ ...orgRoots,
10888
+ ...vaultRoots,
10889
+ ...[machineRoot(), ...vaultRoots].map((root) => join18(root, CONVERSATIONS_DIRNAME))
10890
+ ]);
10400
10891
  if (removed > 0) console.log(`[server] ${removed} fichier(s) temporaire(s) orphelin(s) retir\xE9(s)`);
10401
10892
  })().catch((err) => console.warn("[server] Temp file sweep failed:", err));
10402
10893
  cloudSyncBridge.start().catch((err) => {
@@ -10656,13 +11147,14 @@ async function createLocalServer() {
10656
11147
  app.post("/api/auth/refresh", refreshLimiter, async (req, res) => {
10657
11148
  const auth = req.headers.authorization;
10658
11149
  const jwt = typeof auth === "string" && auth.startsWith("Bearer ") ? auth.slice(7) : "";
11150
+ let mcpClientId;
10659
11151
  try {
10660
- await verifyJwt(jwt);
11152
+ ({ mcpClientId } = await verifyJwt(jwt, { allowMcp: true }));
10661
11153
  } catch {
10662
11154
  res.status(401).json({ error: "Session invalide" });
10663
11155
  return;
10664
11156
  }
10665
- res.json({ token: await signJwt() });
11157
+ res.json({ token: await signJwt(mcpClientId ? { mcpClientId, ttl: MCP_TOKEN_TTL } : {}) });
10666
11158
  });
10667
11159
  app.post("/api/unlock", authLimiter, csrfMiddleware, express.json({ limit: "10kb" }), async (req, res) => {
10668
11160
  try {
@@ -10720,8 +11212,8 @@ async function createLocalServer() {
10720
11212
  }
10721
11213
  encKey = Buffer.from(encryptionKeyHex, "hex");
10722
11214
  } else {
10723
- const { randomBytes: randomBytes4 } = await import("crypto");
10724
- encKey = randomBytes4(32);
11215
+ const { randomBytes: randomBytes5 } = await import("crypto");
11216
+ encKey = randomBytes5(32);
10725
11217
  }
10726
11218
  }
10727
11219
  await writeEncryptedKey(await keyRoot(), masterPassword, encKey);
@@ -11043,16 +11535,16 @@ async function createLocalServer() {
11043
11535
  ]);
11044
11536
  server.closeAllConnections();
11045
11537
  await Promise.race([
11046
- new Promise((resolve4) => wss.close(() => resolve4())),
11047
- new Promise((resolve4) => setTimeout(resolve4, 2e3))
11538
+ new Promise((resolve5) => wss.close(() => resolve5())),
11539
+ new Promise((resolve5) => setTimeout(resolve5, 2e3))
11048
11540
  ]);
11049
11541
  await Promise.race([
11050
- new Promise((resolve4) => server.close(() => resolve4())),
11051
- new Promise((resolve4) => setTimeout(resolve4, 2e3))
11542
+ new Promise((resolve5) => server.close(() => resolve5())),
11543
+ new Promise((resolve5) => setTimeout(resolve5, 2e3))
11052
11544
  ]);
11053
11545
  console.log("[server] Shutdown complete");
11054
11546
  };
11055
- const { initCompanion } = await import("./companion-2LHKU577.js");
11547
+ const { initCompanion } = await import("./companion-W3UMZWGH.js");
11056
11548
  initCompanion();
11057
11549
  return { app, server, wss, shutdown };
11058
11550
  }
@@ -11071,7 +11563,7 @@ var PullEnvError = class extends Error {
11071
11563
  };
11072
11564
  async function promptPassword() {
11073
11565
  process.stderr.write("Master password: ");
11074
- return new Promise((resolve4) => {
11566
+ return new Promise((resolve5) => {
11075
11567
  const stdin = process.stdin;
11076
11568
  const wasRaw = stdin.isRaw;
11077
11569
  if (stdin.isTTY) stdin.setRawMode(true);
@@ -11084,7 +11576,7 @@ async function promptPassword() {
11084
11576
  stdin.pause();
11085
11577
  stdin.removeListener("data", onData);
11086
11578
  process.stderr.write("\n");
11087
- resolve4(input);
11579
+ resolve5(input);
11088
11580
  } else if (ch === "") {
11089
11581
  process.exit(1);
11090
11582
  } else if (ch === "\x7F" || ch === "\b") {
@@ -11182,8 +11674,8 @@ function onTrayServicesChanged(listener) {
11182
11674
  }
11183
11675
 
11184
11676
  // ../server/src/services/tray-icon.ts
11185
- import { mkdir as mkdir12, writeFile as writeFile10, rename as rename6 } from "fs/promises";
11186
- import { join as join17 } from "path";
11677
+ import { mkdir as mkdir13, writeFile as writeFile10, rename as rename6 } from "fs/promises";
11678
+ import { join as join19 } from "path";
11187
11679
 
11188
11680
  // ../server/src/services/png-to-ico.ts
11189
11681
  function isPng(bytes) {
@@ -11343,7 +11835,7 @@ var FETCH_TIMEOUT_MS = 3e3;
11343
11835
  var CACHE_TTL_MS = 10 * 6e4;
11344
11836
  var cache2 = /* @__PURE__ */ new Map();
11345
11837
  function trayIconDir() {
11346
- return join17(machineRoot(), "tray-icons");
11838
+ return join19(machineRoot(), "tray-icons");
11347
11839
  }
11348
11840
  async function resolveTrayIcon(platform = process.platform) {
11349
11841
  const orgId = getCurrentProject()?.orgId;
@@ -11371,8 +11863,8 @@ async function renderThemedIcon(platform) {
11371
11863
  }
11372
11864
  async function writeTrayIcon(stem, png, platform) {
11373
11865
  const dir = trayIconDir();
11374
- await mkdir12(dir, { recursive: true });
11375
- const path = join17(dir, stem);
11866
+ await mkdir13(dir, { recursive: true });
11867
+ const path = join19(dir, stem);
11376
11868
  if (platform === "win32") {
11377
11869
  const ico = pngToIco(png);
11378
11870
  return ico ? writeAtomic(`${path}.ico`, ico) : null;
@@ -11414,7 +11906,7 @@ if (process.env["RUNEYA_DATA_DIR"]) {
11414
11906
  console.log(`[server] Working directory: ${resolved} (from RUNEYA_DATA_DIR)`);
11415
11907
  }
11416
11908
  function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500) {
11417
- return new Promise((resolve4, reject) => {
11909
+ return new Promise((resolve5, reject) => {
11418
11910
  let attempts = 0;
11419
11911
  const wssErrorHandler = () => {
11420
11912
  };
@@ -11435,7 +11927,7 @@ function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500)
11435
11927
  server.listen(port, host, () => {
11436
11928
  server.removeListener("error", onError);
11437
11929
  wss?.removeListener("error", wssErrorHandler);
11438
- resolve4();
11930
+ resolve5();
11439
11931
  });
11440
11932
  };
11441
11933
  tryListen();
@@ -11448,6 +11940,7 @@ async function main() {
11448
11940
  console.log("Hello World 2");
11449
11941
  const { server, wss, shutdown } = await createLocalServer();
11450
11942
  await listenWithRetry(server, env.PORT, env.HOST, wss);
11943
+ await registerInstance();
11451
11944
  console.log(`[server] Local server listening on ${env.HOST}:${env.PORT}`);
11452
11945
  console.log(`[server] tRPC HTTP: http://${env.HOST}:${env.PORT}/api/trpc`);
11453
11946
  console.log(`[server] tRPC WS: ws://${env.HOST}:${env.PORT}`);
@@ -11507,4 +12000,4 @@ export {
11507
12000
  stopProcess,
11508
12001
  userPrefix
11509
12002
  };
11510
- //# sourceMappingURL=src-5TEXF3SU.js.map
12003
+ //# sourceMappingURL=src-LEDGW5KP.js.map