@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.
- package/agent/index.js +33 -22
- package/agent/index.js.map +1 -1
- package/agent-manager-R7BPQO6D.js +11 -0
- package/chat-LPML22J5.js +24 -0
- package/{chunk-7L6AQUBR.js → chunk-4LJQIQMN.js} +6 -2
- package/{chunk-7L6AQUBR.js.map → chunk-4LJQIQMN.js.map} +1 -1
- package/{chunk-7HIPFSOA.js → chunk-57P45FNY.js} +11 -3
- package/chunk-57P45FNY.js.map +1 -0
- package/chunk-6IBPK6UE.js +1063 -0
- package/chunk-6IBPK6UE.js.map +1 -0
- package/{chunk-GXE5TLLJ.js → chunk-KV4GJKXU.js} +17 -17
- package/{chunk-XAFOP677.js → chunk-MZSDTA4A.js} +480 -233
- package/chunk-MZSDTA4A.js.map +1 -0
- package/{chunk-UR3HL373.js → chunk-NPMSBYXC.js} +8 -8
- package/{chunk-P73PTVII.js → chunk-SEPAURTQ.js} +369 -1282
- package/chunk-SEPAURTQ.js.map +1 -0
- package/{chunk-NSS47H26.js → chunk-UD42LACH.js} +348 -78
- package/chunk-UD42LACH.js.map +1 -0
- package/{chunk-IXO5KIO5.js → chunk-VPSQJOSN.js} +23 -2
- package/{chunk-IXO5KIO5.js.map → chunk-VPSQJOSN.js.map} +1 -1
- package/{companion-2LHKU577.js → companion-W3UMZWGH.js} +11 -7
- package/{desktop-installer-page-6JDSXK2E.js → desktop-installer-page-I5FHFDES.js} +11 -8
- package/desktop-installer-page-I5FHFDES.js.map +1 -0
- package/{desktop-launcher-YPNRQMIS.js → desktop-launcher-GD4QPM66.js} +3 -3
- package/external-NMKQPROK.js +387 -0
- package/external-NMKQPROK.js.map +1 -0
- package/index.js +34 -12
- package/index.js.map +1 -1
- package/{install-gate-2NNH7RM2.js → install-gate-PVMGJDWE.js} +2 -2
- package/mcp-server/external.d.ts +44 -0
- package/mcp-server/external.js +2679 -0
- package/mcp-server/external.js.map +1 -0
- package/mcp-server/index.js +142 -40
- package/mcp-server/index.js.map +1 -1
- package/mcp-server/launch.d.ts +19 -1
- package/mcp-server/launch.js +20 -0
- package/mcp-server/launch.js.map +1 -1
- package/package.json +1 -1
- package/settings-manager-XPLBO35P.js +10 -0
- package/src-DCCEUWE6.js +385 -0
- package/{src-5TEXF3SU.js → src-LEDGW5KP.js} +1029 -536
- package/src-LEDGW5KP.js.map +1 -0
- package/{vault-resolver-VK6W5J6W.js → vault-resolver-E7KPKA5C.js} +3 -2
- package/vault-resolver-E7KPKA5C.js.map +1 -0
- package/web/assets/{AgentScalarChatInterface.vue-B-SjOsdj.js → AgentScalarChatInterface.vue-BYszptaB.js} +1 -1
- package/web/assets/{AgentTerminalPage-JzC8DUgu.js → AgentTerminalPage-C3TJ1NL9.js} +1 -1
- package/web/assets/{AiChatModals-D1928AxG.js → AiChatModals-D3scRE3S.js} +1 -1
- package/web/assets/AppLayout-BMQUjPKe.js +1 -0
- package/web/assets/{AppLayout-8sBPr3od.css → AppLayout-tsw5Ow0w.css} +1 -1
- package/web/assets/{AuditPage-BZIJ3_Kr.js → AuditPage-CKcExBws.js} +1 -1
- package/web/assets/{CloudAccountPage-DBv470sT.js → CloudAccountPage-B6oPg0BZ.js} +1 -1
- package/web/assets/{CloudMessagingPage-qCI8-UDL.js → CloudMessagingPage-C5fwVe-F.js} +1 -1
- package/web/assets/DashboardPage-9RV7sx3e.js +1 -0
- package/web/assets/{DashboardPage-D9-PpC3F.css → DashboardPage-FvNuC_e4.css} +1 -1
- package/web/assets/{DirectoryPickerDialog-B5DvDfQP.js → DirectoryPickerDialog-C06BXyfJ.js} +1 -1
- package/web/assets/{EnvironmentForm-Bw8Djnqm.js → EnvironmentForm-CAvDoira.js} +1 -1
- package/web/assets/{EnvironmentFormPage-B59uiYtp.js → EnvironmentFormPage-BmAxhN-m.js} +1 -1
- package/web/assets/{EnvironmentListPage-W9zLDZAg.js → EnvironmentListPage-B8uATPBI.js} +1 -1
- package/web/assets/KanbanBoardPage-CQsOkRUf.js +4 -0
- package/web/assets/KanbanBoardPage-eHmsrFGA.css +1 -0
- package/web/assets/{KanbanListPage-Bn9k8Rpc.js → KanbanListPage-CWZqkjD6.js} +1 -1
- package/web/assets/{LaunchProjectPage-Dc7v1UjQ.js → LaunchProjectPage-saSmC7Pr.js} +1 -1
- package/web/assets/LaunchServicesPage-BkKyLMEz.js +1 -0
- package/web/assets/LaunchServicesPage-DoLDPqQq.css +1 -0
- package/web/assets/{LaunchShell-LonTJpb3.js → LaunchShell-DUrSunB4.js} +1 -1
- package/web/assets/{LogViewer-DkuqWYkQ.js → LogViewer-CZBYkH9J.js} +1 -1
- package/web/assets/{OrgKeyPrompt-By1VjlSN.js → OrgKeyPrompt-DzQ7pwbs.js} +1 -1
- package/web/assets/{ProcessControls-C0skzKGL.js → ProcessControls-BEeoNlNV.js} +1 -1
- package/web/assets/{ProcessDetailPage-BvPX6z_X.js → ProcessDetailPage-CQgu5uKm.js} +1 -1
- package/web/assets/{ProjectCreateDialog-C4o9u15L.js → ProjectCreateDialog-BolrhRD-.js} +1 -1
- package/web/assets/{ProjectLocationGate-BJ1CSQt0.js → ProjectLocationGate-DhMVdIjN.js} +1 -1
- package/web/assets/{ProjectPublishDialog-Cp-zXdGm.js → ProjectPublishDialog-DsZNWrfz.js} +1 -1
- package/web/assets/{RandomJobTestPage-DF4a1U13.js → RandomJobTestPage-BUS2Ty6-.js} +1 -1
- package/web/assets/ScenarioEditorPage-MIAGh2us.js +14 -0
- package/web/assets/{SendToChatDialog-Dg2SHkVD.js → SendToChatDialog-BxWsyv29.js} +1 -1
- package/web/assets/{ServiceControlsBar-q2VHWaxK.js → ServiceControlsBar-DqDPAZt7.js} +1 -1
- package/web/assets/ServiceDetailPage-hkSAzZ-l.js +2 -0
- package/web/assets/{ServiceFormPage-tT_n7WUy.js → ServiceFormPage-DXqhqXg8.js} +1 -1
- package/web/assets/SettingsPage-4pKUgKPm.css +1 -0
- package/web/assets/SettingsPage-DS2YhMIM.js +2086 -0
- package/web/assets/{UnlockPage-BKAdMJkb.js → UnlockPage-s8SgVcKk.js} +1 -1
- package/web/assets/{VaultMigrationPage-D9KUTTss.js → VaultMigrationPage-s0b44MDM.js} +1 -1
- package/web/assets/{VersionBadge-moKP5bAv.js → VersionBadge-BmArk24p.js} +1 -1
- package/web/assets/{WikiPage-hLUndDxo.js → WikiPage-DADu8WUA.js} +1 -1
- package/web/assets/{WikiView-rrjXRR72.js → WikiView-DtGwIt_Q.js} +1 -1
- package/web/assets/{add-scalar-classes-CBfRr8B2.js → add-scalar-classes-dWEWPlFD.js} +2 -2
- package/web/assets/{agent.store-B3dVemmT.js → agent.store-CKLAkF9a.js} +1 -1
- package/web/assets/app-mode-BVpTuh0i.js +1 -0
- package/web/assets/auth-Cg9BWTfK.js +2 -0
- package/web/assets/{cloud-chat.store-Wg1poFem.js → cloud-chat.store-b-BHGz7I.js} +1 -1
- package/web/assets/{cloud-client-DQlUaoAw.js → cloud-client-D_zCiW0s.js} +2 -2
- package/web/assets/{cloud-session-DRUqcSGP.js → cloud-session-DOaDpZTy.js} +2 -2
- package/web/assets/dist-N4nrnbnA.js +1 -0
- package/web/assets/{editor.store-SoH48ux2.js → editor.store-DCAZSeVG.js} +1 -1
- package/web/assets/i18n-D0afQEGs.js +1 -0
- package/web/assets/{index-D_x2s8Ut.js → index-D8VRbyoP.js} +101 -101
- package/web/assets/index-DsezNbSM.css +1 -0
- package/web/assets/{modal-CpsVK_dA.js → modal-qYgTMkYb.js} +1 -1
- package/web/assets/{project-launch.store-C3fZeRGb.js → project-launch.store-y7JBMNiE.js} +1 -1
- package/web/assets/{project.store-BEk4fciH.js → project.store-DGJr0Qr9.js} +1 -1
- package/web/assets/{scenario.store-D-JrRiVO.js → scenario.store-YWHL2_wu.js} +3 -3
- package/web/assets/{service.store-B5uYZtyM.js → service.store-5jWsJxPx.js} +1 -1
- package/web/assets/{settings-scope-Blnwj3rR.js → settings-scope-Bv7QGPrt.js} +1 -1
- package/web/assets/{settings.store-Bo3FYs1o.js → settings.store-bEsB7oKb.js} +1 -1
- package/web/assets/{src-CAwAaB5j.js → src-C-D_y7ou.js} +1 -1
- package/web/assets/{src-DValZNJU.js → src-Dnwk6NpV.js} +5 -5
- package/web/assets/src-g1DRo8xZ.css +1 -0
- package/web/assets/{trpc-DbsE5GvQ.js → trpc-Drabzwpd.js} +3 -3
- package/web/assets/{useCloudOrg-DygUBG1a.js → useCloudOrg-BAQEtFPl.js} +1 -1
- package/web/assets/{useEnvVarAutocomplete-DmYNOXgd.js → useEnvVarAutocomplete-BqGTODUv.js} +1 -1
- package/web/assets/{useProjectCatalog-D2mHB7dg.js → useProjectCatalog-yocjIKt6.js} +1 -1
- package/web/assets/{useProjectCloudOrg-avTCVold.js → useProjectCloudOrg-CEN9-_OA.js} +1 -1
- package/web/assets/{useServiceUrlHost-Q3fzGox3.js → useServiceUrlHost-BuoPEXZX.js} +1 -1
- package/web/assets/{vue-draggable-plus-WgrMXXmE.js → vue-draggable-plus-DX4rTBiu.js} +1 -1
- package/web/index.html +19 -18
- package/agent-manager-M4NA5YFD.js +0 -11
- package/chat-X3X527EX.js +0 -23
- package/chunk-7HIPFSOA.js.map +0 -1
- package/chunk-NSS47H26.js.map +0 -1
- package/chunk-P73PTVII.js.map +0 -1
- package/chunk-XAFOP677.js.map +0 -1
- package/desktop-installer-page-6JDSXK2E.js.map +0 -1
- package/settings-manager-32SVMAFN.js +0 -10
- package/src-5TEXF3SU.js.map +0 -1
- package/web/assets/AppLayout-CVQIBYqg.js +0 -1
- package/web/assets/DashboardPage-D0UZM0mr.js +0 -1
- package/web/assets/KanbanBoardPage-DRACMk_D.css +0 -1
- package/web/assets/KanbanBoardPage-DwPqoJDJ.js +0 -4
- package/web/assets/LaunchServicesPage-CXUS9O3y.js +0 -1
- package/web/assets/LaunchServicesPage-DJDIN8HU.css +0 -1
- package/web/assets/ScenarioEditorPage-Dl-gEkJI.js +0 -14
- package/web/assets/ServiceDetailPage-CJ8WaFod.js +0 -2
- package/web/assets/SettingsPage-CWakkv0V.js +0 -2086
- package/web/assets/SettingsPage-aGSxZw1n.css +0 -1
- package/web/assets/auth-BCrvnP1U.js +0 -2
- package/web/assets/dist-DoChtT9t.js +0 -1
- package/web/assets/i18n-BszFg0ko.js +0 -1
- package/web/assets/index-C_JV7fYy.css +0 -1
- package/web/assets/src-DuBXN1hu.css +0 -1
- /package/{agent-manager-M4NA5YFD.js.map → agent-manager-R7BPQO6D.js.map} +0 -0
- /package/{chat-X3X527EX.js.map → chat-LPML22J5.js.map} +0 -0
- /package/{chunk-GXE5TLLJ.js.map → chunk-KV4GJKXU.js.map} +0 -0
- /package/{chunk-UR3HL373.js.map → chunk-NPMSBYXC.js.map} +0 -0
- /package/{companion-2LHKU577.js.map → companion-W3UMZWGH.js.map} +0 -0
- /package/{desktop-launcher-YPNRQMIS.js.map → desktop-launcher-GD4QPM66.js.map} +0 -0
- /package/{install-gate-2NNH7RM2.js.map → install-gate-PVMGJDWE.js.map} +0 -0
- /package/{settings-manager-32SVMAFN.js.map → settings-manager-XPLBO35P.js.map} +0 -0
- /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-
|
|
68
|
+
} from "./chunk-SEPAURTQ.js";
|
|
69
|
+
import "./chunk-6IBPK6UE.js";
|
|
66
70
|
import {
|
|
67
71
|
settingsManager
|
|
68
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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,
|
|
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(
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
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((
|
|
1335
|
-
wake =
|
|
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
|
|
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 (
|
|
1488
|
-
|
|
1489
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
1624
|
-
|
|
1625
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
1655
|
-
|
|
1656
|
-
|
|
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
|
-
|
|
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((
|
|
2685
|
-
wake =
|
|
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((
|
|
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((
|
|
3371
|
+
return new Promise((resolve5) => {
|
|
3314
3372
|
execFile3(whichCommand(), [command], { timeout: 5e3, windowsHide: true }, (err) => {
|
|
3315
|
-
|
|
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 =
|
|
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
|
|
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 (
|
|
3704
|
-
|
|
3705
|
-
|
|
3761
|
+
if (resolve5) {
|
|
3762
|
+
resolve5();
|
|
3763
|
+
resolve5 = null;
|
|
3706
3764
|
}
|
|
3707
3765
|
},
|
|
3708
3766
|
() => {
|
|
3709
3767
|
done = true;
|
|
3710
|
-
if (
|
|
3711
|
-
|
|
3712
|
-
|
|
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
|
-
|
|
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((
|
|
4134
|
+
await new Promise((resolve5) => {
|
|
4077
4135
|
const onAbort = () => {
|
|
4078
4136
|
bus.listeners.delete(notify);
|
|
4079
|
-
|
|
4137
|
+
resolve5();
|
|
4080
4138
|
};
|
|
4081
4139
|
const notify = () => {
|
|
4082
4140
|
signal.removeEventListener("abort", onAbort);
|
|
4083
|
-
|
|
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
|
-
|
|
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((
|
|
4658
|
+
const answer = await new Promise((resolve5, reject) => {
|
|
4601
4659
|
const key = pendingNodeKey(conversationId, node.id);
|
|
4602
|
-
pendingNodeInputs.set(key,
|
|
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((
|
|
5065
|
+
const answer = await new Promise((resolve5, reject) => {
|
|
5008
5066
|
const key = pendingNodeKey(conversationId, node.id);
|
|
5009
|
-
pendingNodeInputs.set(key,
|
|
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((
|
|
5620
|
+
await new Promise((resolve5) => {
|
|
5563
5621
|
const onAbort = () => {
|
|
5564
5622
|
job.listeners.delete(notify);
|
|
5565
|
-
|
|
5623
|
+
resolve5();
|
|
5566
5624
|
};
|
|
5567
5625
|
const notify = () => {
|
|
5568
5626
|
signal.removeEventListener("abort", onAbort);
|
|
5569
|
-
|
|
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
|
-
|
|
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((
|
|
5729
|
-
wake =
|
|
5730
|
-
signal?.addEventListener("abort", () =>
|
|
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/
|
|
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
|
|
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:
|
|
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
|
|
5863
|
-
abortScenario(
|
|
5864
|
-
activeSessionRegistry.kill(
|
|
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
|
|
5875
|
-
abortScenario(
|
|
5876
|
-
activeSessionRegistry.kill(
|
|
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
|
|
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
|
-
|
|
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(
|
|
5996
|
-
boardId:
|
|
5997
|
-
cardId:
|
|
5998
|
-
runner:
|
|
5999
|
-
model:
|
|
6000
|
-
scenarioId:
|
|
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:
|
|
6005
|
-
orgId:
|
|
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(
|
|
6008
|
-
boardId:
|
|
6009
|
-
columnId:
|
|
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:
|
|
6012
|
-
runner:
|
|
6013
|
-
model:
|
|
6014
|
-
scenarioId:
|
|
6015
|
-
projectId:
|
|
6016
|
-
orgId:
|
|
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(
|
|
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(
|
|
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
|
|
6474
|
+
let resolve5 = null;
|
|
6033
6475
|
const handler = () => {
|
|
6034
6476
|
if (queue.length >= 50) queue.shift();
|
|
6035
6477
|
queue.push(kanbanQueue.getState());
|
|
6036
|
-
if (
|
|
6037
|
-
|
|
6038
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
6064
|
-
import { z as
|
|
6065
|
-
import { TRPCError as
|
|
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
|
|
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 =
|
|
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(),
|
|
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
|
|
6128
|
-
const path =
|
|
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
|
|
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 =
|
|
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(
|
|
6158
|
-
includeArchived:
|
|
6159
|
-
organizationId:
|
|
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(
|
|
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(
|
|
6225
|
-
query:
|
|
6226
|
-
createdBy:
|
|
6227
|
-
boardId:
|
|
6228
|
-
limit:
|
|
6229
|
-
organizationId:
|
|
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(
|
|
6238
|
-
boardId:
|
|
6239
|
-
cardId:
|
|
6240
|
-
limit:
|
|
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(
|
|
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(
|
|
6256
|
-
boardId:
|
|
6257
|
-
cardId:
|
|
6258
|
-
connectionId:
|
|
6259
|
-
externalId:
|
|
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(
|
|
6271
|
-
boardId:
|
|
6272
|
-
cardId:
|
|
6273
|
-
limit:
|
|
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(
|
|
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(
|
|
6310
|
-
boardId:
|
|
6311
|
-
cardId:
|
|
6312
|
-
parentCardId:
|
|
6313
|
-
parentBoardId:
|
|
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
|
|
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(
|
|
6331
|
-
placeCard: protectedProcedure.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
|
|
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(
|
|
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
|
|
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(
|
|
6348
|
-
boardId:
|
|
6349
|
-
cardId:
|
|
6350
|
-
targetColumn:
|
|
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(
|
|
6358
|
-
boardId:
|
|
6359
|
-
cardId:
|
|
6360
|
-
comment:
|
|
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(
|
|
6368
|
-
boardId:
|
|
6369
|
-
column:
|
|
6370
|
-
title:
|
|
6371
|
-
content:
|
|
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(
|
|
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(
|
|
6399
|
-
boardId:
|
|
6400
|
-
cardId:
|
|
6401
|
-
title:
|
|
6402
|
-
content:
|
|
6403
|
-
priority:
|
|
6404
|
-
color:
|
|
6405
|
-
groups:
|
|
6406
|
-
deadline:
|
|
6407
|
-
assignees:
|
|
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(
|
|
6419
|
-
boardId:
|
|
6420
|
-
cardId:
|
|
6421
|
-
archived:
|
|
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(
|
|
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(
|
|
6440
|
-
attachmentId:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
6925
|
+
throw new TRPCError19({ code: "NOT_FOUND", message: `Attachment ${id} not found` });
|
|
6461
6926
|
}
|
|
6462
6927
|
if (!res.ok) {
|
|
6463
|
-
throw new
|
|
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
|
|
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
|
|
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
|
|
6487
|
-
import { TRPCError as
|
|
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(
|
|
6491
|
-
agentId:
|
|
6492
|
-
cwd:
|
|
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:
|
|
6495
|
-
projectId:
|
|
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:
|
|
6498
|
-
cols:
|
|
6499
|
-
rows:
|
|
6500
|
-
title:
|
|
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(
|
|
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(
|
|
6529
|
-
agentId:
|
|
6530
|
-
sessionId:
|
|
6531
|
-
title:
|
|
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(
|
|
6542
|
-
agentId:
|
|
6543
|
-
sessionId:
|
|
6544
|
-
data:
|
|
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(
|
|
6555
|
-
agentId:
|
|
6556
|
-
sessionId:
|
|
6557
|
-
cols:
|
|
6558
|
-
rows:
|
|
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(
|
|
6569
|
-
agentId:
|
|
6570
|
-
sessionId:
|
|
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(
|
|
6581
|
-
agentId:
|
|
6582
|
-
sessionId:
|
|
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:
|
|
7049
|
+
replay: z24.boolean().optional()
|
|
6585
7050
|
})).subscription(async function* ({ input }) {
|
|
6586
7051
|
const queue = [];
|
|
6587
|
-
let
|
|
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 (
|
|
6598
|
-
|
|
6599
|
-
|
|
7062
|
+
if (resolve5) {
|
|
7063
|
+
resolve5();
|
|
7064
|
+
resolve5 = null;
|
|
6600
7065
|
}
|
|
6601
7066
|
},
|
|
6602
7067
|
() => {
|
|
6603
7068
|
done = true;
|
|
6604
|
-
if (
|
|
6605
|
-
|
|
6606
|
-
|
|
7069
|
+
if (resolve5) {
|
|
7070
|
+
resolve5();
|
|
7071
|
+
resolve5 = null;
|
|
6607
7072
|
}
|
|
6608
7073
|
}
|
|
6609
7074
|
);
|
|
6610
7075
|
} catch (err) {
|
|
6611
|
-
throw new
|
|
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
|
-
|
|
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
|
|
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(
|
|
6637
|
-
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((
|
|
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
|
|
6646
|
-
import { TRPCError as
|
|
6647
|
-
import { readFile as
|
|
6648
|
-
import { resolve as resolvePath, join as
|
|
6649
|
-
var SCOPE_SCHEMA =
|
|
6650
|
-
|
|
6651
|
-
|
|
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 =
|
|
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 =
|
|
6674
|
-
|
|
6675
|
-
|
|
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
|
|
7147
|
+
throw new TRPCError21({ code: "BAD_REQUEST", message: "Sous-chemin invalide" });
|
|
6683
7148
|
}
|
|
6684
7149
|
const dataDirAbs = resolvePath(env.DATA_DIR);
|
|
6685
|
-
await
|
|
6686
|
-
const base =
|
|
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
|
|
7154
|
+
throw new TRPCError21({ code: "FORBIDDEN", message: "Hors du dossier de donn\xE9es du serveur" });
|
|
6690
7155
|
}
|
|
6691
|
-
await
|
|
6692
|
-
const root = await
|
|
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
|
|
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
|
|
7165
|
+
if (!service) throw new TRPCError21({ code: "NOT_FOUND", message: "Service introuvable" });
|
|
6701
7166
|
const agentId = await resolveEffectiveAgentId(service);
|
|
6702
|
-
if (!agentId) throw new
|
|
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 =
|
|
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
|
|
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
|
|
7208
|
+
real = await realpath2(target);
|
|
6744
7209
|
} catch {
|
|
6745
|
-
throw new
|
|
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
|
|
7213
|
+
throw new TRPCError21({ code: "FORBIDDEN", message: "Hors du dossier autoris\xE9" });
|
|
6749
7214
|
}
|
|
6750
7215
|
return real;
|
|
6751
7216
|
}
|
|
6752
|
-
const parent =
|
|
7217
|
+
const parent = dirname5(target);
|
|
6753
7218
|
let realParent;
|
|
6754
7219
|
try {
|
|
6755
|
-
realParent = await
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
6785
|
-
if (s.size > MAX_FILE_BYTES) throw new
|
|
6786
|
-
const content = await
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
7278
|
+
throw new TRPCError21({ code: "CONFLICT", message: "Fichier d\xE9j\xE0 existant" });
|
|
6814
7279
|
} catch (err) {
|
|
6815
|
-
if (err instanceof
|
|
7280
|
+
if (err instanceof TRPCError21) throw err;
|
|
6816
7281
|
}
|
|
6817
|
-
await
|
|
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(
|
|
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
|
|
7292
|
+
await mkdir7(dir, { recursive: true });
|
|
6828
7293
|
return { path: input.path, ok: true };
|
|
6829
7294
|
}),
|
|
6830
|
-
rename: protectedProcedure.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
|
|
7304
|
+
throw new TRPCError21({ code: "CONFLICT", message: "La cible existe d\xE9j\xE0" });
|
|
6840
7305
|
} catch (err) {
|
|
6841
|
-
if (err instanceof
|
|
7306
|
+
if (err instanceof TRPCError21) throw err;
|
|
6842
7307
|
}
|
|
6843
|
-
await
|
|
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(
|
|
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
|
|
7319
|
+
if (!service) throw new TRPCError21({ code: "NOT_FOUND", message: "Service introuvable" });
|
|
6855
7320
|
const agentId = await resolveEffectiveAgentId(service);
|
|
6856
|
-
if (!agentId) throw new
|
|
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(
|
|
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
|
|
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
|
|
6887
|
-
import { TRPCError as
|
|
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
|
|
7358
|
+
import { join as join10 } from "path";
|
|
6892
7359
|
async function isMaterialized(root) {
|
|
6893
|
-
return access(
|
|
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
|
|
6969
|
-
import { mkdir as
|
|
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
|
|
6975
|
-
import { dirname as
|
|
6976
|
-
import { randomUUID as
|
|
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
|
|
7445
|
+
var FILE2 = "sync-doc.bin";
|
|
6979
7446
|
function cachePath(orgId) {
|
|
6980
|
-
return
|
|
7447
|
+
return join11(orgVaultRoot(orgId), FILE2);
|
|
6981
7448
|
}
|
|
6982
7449
|
async function readSyncDoc(orgId) {
|
|
6983
7450
|
try {
|
|
6984
|
-
return new Uint8Array(await
|
|
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(
|
|
7008
|
-
const tmp = `${path}.${process.pid}.${
|
|
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 =
|
|
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((
|
|
7112
|
-
this.markOpened =
|
|
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
|
|
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((
|
|
7974
|
-
timer = setTimeout(
|
|
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
|
|
7992
|
-
import { join as
|
|
7993
|
-
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as
|
|
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 =
|
|
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
|
-
|
|
8514
|
+
var ENCRYPTED_SUBDIRS = [CONVERSATIONS_DIRNAME];
|
|
8515
|
+
async function jsonNamesIn(dir) {
|
|
8048
8516
|
try {
|
|
8049
|
-
const entries2 = await readdir3(
|
|
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 =
|
|
8564
|
+
const path = join12(vaultRoot, name);
|
|
8090
8565
|
let parsed;
|
|
8091
8566
|
try {
|
|
8092
|
-
parsed = JSON.parse(await
|
|
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 =
|
|
8107
|
-
await
|
|
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
|
|
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(
|
|
8113
|
-
await copyFile(
|
|
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 =
|
|
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
|
|
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
|
|
8654
|
+
throw new TRPCError22({ code: "FORBIDDEN", message: err.message });
|
|
8179
8655
|
}
|
|
8180
8656
|
if (err instanceof CanaryUnavailableError) {
|
|
8181
|
-
throw new
|
|
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
|
-
|
|
8199
|
-
orgId:
|
|
8200
|
-
cloudUrl:
|
|
8201
|
-
projectId:
|
|
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:
|
|
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
|
|
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
|
-
|
|
8247
|
-
orgId:
|
|
8248
|
-
cloudUrl:
|
|
8249
|
-
projectId:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
8301
|
-
orgId:
|
|
8302
|
-
keyHex:
|
|
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
|
|
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(
|
|
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(
|
|
8348
|
-
orgId:
|
|
8349
|
-
keyHex:
|
|
8350
|
-
masterPassword:
|
|
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(
|
|
8381
|
-
orgId:
|
|
8382
|
-
masterPassword:
|
|
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
|
|
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
|
|
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
|
-
|
|
8416
|
-
orgId:
|
|
8417
|
-
projectId:
|
|
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
|
-
|
|
8434
|
-
orgId:
|
|
8435
|
-
projectId:
|
|
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
|
|
8449
|
-
throw new
|
|
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
|
|
8933
|
+
throw new TRPCError22({ code: "FORBIDDEN", message: code, cause: new Error(detail) });
|
|
8458
8934
|
}
|
|
8459
8935
|
if (res.status === 404) {
|
|
8460
|
-
throw new
|
|
8936
|
+
throw new TRPCError22({ code: "NOT_FOUND", message: "PROJECT_NOT_IN_ORG", cause: new Error(detail) });
|
|
8461
8937
|
}
|
|
8462
|
-
throw new
|
|
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(
|
|
8492
|
-
orgs:
|
|
8493
|
-
orgId:
|
|
8494
|
-
accountId:
|
|
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(
|
|
8522
|
-
orgs:
|
|
8523
|
-
orgId:
|
|
8524
|
-
name:
|
|
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(
|
|
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
|
|
8612
|
-
import { isAbsolute as isAbsolute3, join as
|
|
8613
|
-
import { z as
|
|
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 =
|
|
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("~") ?
|
|
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
|
|
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:
|
|
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(
|
|
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 =
|
|
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:
|
|
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(
|
|
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(
|
|
9160
|
+
checkProjectTarget: protectedProcedure.input(z28.object({
|
|
8685
9161
|
path: PATH_SCHEMA,
|
|
8686
|
-
projectId:
|
|
8687
|
-
orgId:
|
|
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
|
|
9182
|
+
import { z as z29 } from "zod";
|
|
8707
9183
|
import { isAbsolute as isAbsolute5 } from "path";
|
|
8708
|
-
import { TRPCError as
|
|
9184
|
+
import { TRPCError as TRPCError23 } from "@trpc/server";
|
|
8709
9185
|
|
|
8710
9186
|
// ../server/src/services/desktop-launcher.ts
|
|
8711
|
-
import { execFile as
|
|
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(
|
|
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(
|
|
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:
|
|
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(
|
|
8799
|
-
name:
|
|
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:
|
|
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:
|
|
8809
|
-
agentPort:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
8928
|
-
import { join as
|
|
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 =
|
|
8959
|
-
await
|
|
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(
|
|
8963
|
-
await copyFile2(
|
|
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
|
|
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 =
|
|
9459
|
+
const tmp = join14(legacyRoot, `${name}.tmp`);
|
|
8979
9460
|
await writeFile7(tmp, JSON.stringify(converted, null, 2), "utf-8");
|
|
8980
|
-
await rename3(tmp,
|
|
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
|
|
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
|
|
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 =
|
|
9478
|
+
const from = join14(legacyRoot, name);
|
|
8998
9479
|
if (!await exists2(from)) continue;
|
|
8999
|
-
const to =
|
|
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 =
|
|
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
|
|
9017
|
-
import { join as
|
|
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
|
|
9031
|
-
const to =
|
|
9032
|
-
const contents = await
|
|
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(
|
|
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
|
|
9070
|
-
import { randomUUID as
|
|
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
|
|
9553
|
+
readFile as readFile10,
|
|
9073
9554
|
writeFile as writeFile9,
|
|
9074
9555
|
rename as rename5,
|
|
9075
|
-
mkdir as
|
|
9556
|
+
mkdir as mkdir12,
|
|
9076
9557
|
access as access2,
|
|
9077
9558
|
readdir as readdir6
|
|
9078
9559
|
} from "fs/promises";
|
|
9079
|
-
import { join as
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
9232
|
-
const legacyOld =
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
9737
|
+
legacyEnvs[name] = await readEncrypted(join16(envsDir, file));
|
|
9257
9738
|
}
|
|
9258
|
-
const servicesDir =
|
|
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(
|
|
9745
|
+
const data = await readEncrypted(join16(servicesDir, file));
|
|
9265
9746
|
if (data?.label) legacyServices[name] = data;
|
|
9266
9747
|
}
|
|
9267
|
-
const overridesDir =
|
|
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(
|
|
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 =
|
|
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(
|
|
9766
|
+
const data = await readEncrypted(join16(parsersDir, file));
|
|
9286
9767
|
if (data?.id) legacyParsers.push(data);
|
|
9287
9768
|
}
|
|
9288
|
-
const projectMonoId =
|
|
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] =
|
|
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 =
|
|
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 =
|
|
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 ||
|
|
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 ||
|
|
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 ||
|
|
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:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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[
|
|
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 =
|
|
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
|
|
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(
|
|
9648
|
-
await atomicWrite(
|
|
9649
|
-
await atomicWrite(
|
|
9650
|
-
await atomicWrite(
|
|
9651
|
-
await atomicWrite(
|
|
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 =
|
|
10146
|
+
const keyJsonPath = join16(dataDir, dir, "dbs", "encryption-key.json");
|
|
9666
10147
|
try {
|
|
9667
|
-
const raw = await
|
|
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:
|
|
9686
|
-
const abDataDir =
|
|
10166
|
+
const { resolve: resolve5 } = await import("path");
|
|
10167
|
+
const abDataDir = resolve5(dataDir);
|
|
9687
10168
|
const candidateDirs = [
|
|
9688
|
-
|
|
9689
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
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[
|
|
10262
|
+
flatOverrides[randomUUID9()] = makeVariable(cleaned, value);
|
|
9782
10263
|
}
|
|
9783
10264
|
}
|
|
9784
10265
|
}
|
|
9785
10266
|
if (Object.keys(flatOverrides).length > 0) {
|
|
9786
|
-
await atomicWrite(
|
|
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(
|
|
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:
|
|
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 =
|
|
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(
|
|
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
|
|
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(
|
|
10361
|
+
await stat10(join17(dataDir, ENCRYPTED_KEY_FILENAME2));
|
|
9881
10362
|
hasEncFile = true;
|
|
9882
10363
|
} catch {
|
|
9883
10364
|
}
|
|
9884
10365
|
try {
|
|
9885
|
-
await stat10(
|
|
10366
|
+
await stat10(join17(dataDir, VERIFY_FILENAME2));
|
|
9886
10367
|
hasVerify = true;
|
|
9887
10368
|
} catch {
|
|
9888
10369
|
}
|
|
9889
10370
|
try {
|
|
9890
|
-
await stat10(
|
|
10371
|
+
await stat10(join17(dataDir, "projects.json"));
|
|
9891
10372
|
hasData = true;
|
|
9892
10373
|
} catch {
|
|
9893
10374
|
}
|
|
9894
10375
|
try {
|
|
9895
|
-
await stat10(
|
|
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
|
|
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 =
|
|
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 (
|
|
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
|
|
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:
|
|
10724
|
-
encKey =
|
|
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((
|
|
11047
|
-
new Promise((
|
|
11538
|
+
new Promise((resolve5) => wss.close(() => resolve5())),
|
|
11539
|
+
new Promise((resolve5) => setTimeout(resolve5, 2e3))
|
|
11048
11540
|
]);
|
|
11049
11541
|
await Promise.race([
|
|
11050
|
-
new Promise((
|
|
11051
|
-
new Promise((
|
|
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-
|
|
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((
|
|
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
|
-
|
|
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
|
|
11186
|
-
import { join as
|
|
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
|
|
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
|
|
11375
|
-
const path =
|
|
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((
|
|
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
|
-
|
|
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-
|
|
12003
|
+
//# sourceMappingURL=src-LEDGW5KP.js.map
|