@runeya/runeya 2.0.145 → 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 +26 -21
- package/agent/index.js.map +1 -1
- package/agent-manager-R7BPQO6D.js +11 -0
- package/{chat-XYCHMTFE.js → chat-LPML22J5.js} +8 -7
- package/{chunk-NCO4OOYT.js → chunk-4LJQIQMN.js} +2 -2
- package/chunk-6IBPK6UE.js +1063 -0
- package/chunk-6IBPK6UE.js.map +1 -0
- package/{chunk-QF56Z6LI.js → chunk-KV4GJKXU.js} +3 -3
- package/{chunk-FE53XX4M.js → chunk-NPMSBYXC.js} +3 -3
- package/{chunk-ONVAH65J.js → chunk-SEPAURTQ.js} +283 -1266
- package/chunk-SEPAURTQ.js.map +1 -0
- package/{chunk-Q4RPY3VW.js → chunk-UD42LACH.js} +25 -11
- package/{chunk-Q4RPY3VW.js.map → chunk-UD42LACH.js.map} +1 -1
- package/{chunk-KS7A5CTW.js → chunk-VPSQJOSN.js} +12 -2
- package/{chunk-KS7A5CTW.js.map → chunk-VPSQJOSN.js.map} +1 -1
- package/{companion-HBYSY2D5.js → companion-W3UMZWGH.js} +5 -5
- package/external-NMKQPROK.js +387 -0
- package/external-NMKQPROK.js.map +1 -0
- package/index.js +15 -6
- package/index.js.map +1 -1
- package/{install-gate-JAEK4BDZ.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 +135 -39
- 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-IODFUZ7A.js → src-LEDGW5KP.js} +951 -499
- package/src-LEDGW5KP.js.map +1 -0
- package/{vault-resolver-PN3TYCLJ.js → vault-resolver-E7KPKA5C.js} +2 -2
- package/web/assets/{AgentScalarChatInterface.vue-npQgDxeP.js → AgentScalarChatInterface.vue-BYszptaB.js} +1 -1
- package/web/assets/{AgentTerminalPage-BTH3yCS2.js → AgentTerminalPage-C3TJ1NL9.js} +1 -1
- package/web/assets/{AiChatModals-It9jnB26.js → AiChatModals-D3scRE3S.js} +1 -1
- package/web/assets/{AppLayout-BTg2CADo.js → AppLayout-BMQUjPKe.js} +1 -1
- package/web/assets/{AuditPage-2PKKGLZs.js → AuditPage-CKcExBws.js} +1 -1
- package/web/assets/{CloudAccountPage-CHL61gLe.js → CloudAccountPage-B6oPg0BZ.js} +1 -1
- package/web/assets/{CloudMessagingPage-cgKnABPH.js → CloudMessagingPage-C5fwVe-F.js} +1 -1
- package/web/assets/{DashboardPage-CRntTMKY.js → DashboardPage-9RV7sx3e.js} +1 -1
- package/web/assets/{DirectoryPickerDialog-DCyhT3-W.js → DirectoryPickerDialog-C06BXyfJ.js} +1 -1
- package/web/assets/{EnvironmentForm-C96PFWRE.js → EnvironmentForm-CAvDoira.js} +1 -1
- package/web/assets/{EnvironmentFormPage-ChorAd63.js → EnvironmentFormPage-BmAxhN-m.js} +1 -1
- package/web/assets/{EnvironmentListPage-Dw9ziaI8.js → EnvironmentListPage-B8uATPBI.js} +1 -1
- package/web/assets/{KanbanBoardPage-BZHbEj-q.js → KanbanBoardPage-CQsOkRUf.js} +3 -3
- package/web/assets/{KanbanListPage-B0jlfKqs.js → KanbanListPage-CWZqkjD6.js} +1 -1
- package/web/assets/{LaunchProjectPage-BVs1JwbV.js → LaunchProjectPage-saSmC7Pr.js} +1 -1
- package/web/assets/{LaunchServicesPage-PBYJly9N.js → LaunchServicesPage-BkKyLMEz.js} +1 -1
- package/web/assets/{LaunchShell-CGNYr-do.js → LaunchShell-DUrSunB4.js} +1 -1
- package/web/assets/{LogViewer-BDObx29F.js → LogViewer-CZBYkH9J.js} +1 -1
- package/web/assets/{OrgKeyPrompt-BbcwiwGw.js → OrgKeyPrompt-DzQ7pwbs.js} +1 -1
- package/web/assets/{ProcessControls-DY0HTKde.js → ProcessControls-BEeoNlNV.js} +1 -1
- package/web/assets/{ProcessDetailPage-BZ9kIQqn.js → ProcessDetailPage-CQgu5uKm.js} +1 -1
- package/web/assets/{ProjectCreateDialog-C1wVDPcK.js → ProjectCreateDialog-BolrhRD-.js} +1 -1
- package/web/assets/{ProjectLocationGate-bUd_tFpI.js → ProjectLocationGate-DhMVdIjN.js} +1 -1
- package/web/assets/{ProjectPublishDialog-APUS1WEN.js → ProjectPublishDialog-DsZNWrfz.js} +1 -1
- package/web/assets/{RandomJobTestPage-BFNC827R.js → RandomJobTestPage-BUS2Ty6-.js} +1 -1
- package/web/assets/{ScenarioEditorPage-DFVsMVu5.js → ScenarioEditorPage-MIAGh2us.js} +1 -1
- package/web/assets/{SendToChatDialog-QHIkxNio.js → SendToChatDialog-BxWsyv29.js} +1 -1
- package/web/assets/{ServiceControlsBar-_UQXb6g0.js → ServiceControlsBar-DqDPAZt7.js} +1 -1
- package/web/assets/{ServiceDetailPage-DGIXgHwR.js → ServiceDetailPage-hkSAzZ-l.js} +1 -1
- package/web/assets/{ServiceFormPage-RliAfS3_.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-17EgfpJs.js → UnlockPage-s8SgVcKk.js} +1 -1
- package/web/assets/{VaultMigrationPage-DeMxpXXJ.js → VaultMigrationPage-s0b44MDM.js} +1 -1
- package/web/assets/{VersionBadge-DBquJl76.js → VersionBadge-BmArk24p.js} +1 -1
- package/web/assets/{WikiPage-GtwpJPqf.js → WikiPage-DADu8WUA.js} +1 -1
- package/web/assets/{WikiView-BqaqxMxW.js → WikiView-DtGwIt_Q.js} +1 -1
- package/web/assets/{add-scalar-classes-DhSSqZ5l.js → add-scalar-classes-dWEWPlFD.js} +1 -1
- package/web/assets/{agent.store-Co44dMZf.js → agent.store-CKLAkF9a.js} +1 -1
- package/web/assets/{auth-Bk1GP5sB.js → auth-Cg9BWTfK.js} +2 -2
- package/web/assets/{cloud-chat.store-CadB14dZ.js → cloud-chat.store-b-BHGz7I.js} +1 -1
- package/web/assets/{cloud-client-0zD_NI6q.js → cloud-client-D_zCiW0s.js} +2 -2
- package/web/assets/{cloud-session-Cjw54Y--.js → cloud-session-DOaDpZTy.js} +2 -2
- package/web/assets/{editor.store-DmM6ZlTm.js → editor.store-DCAZSeVG.js} +1 -1
- package/web/assets/i18n-D0afQEGs.js +1 -0
- package/web/assets/{index-B9DJf2V0.js → index-D8VRbyoP.js} +36 -36
- package/web/assets/index-DsezNbSM.css +1 -0
- package/web/assets/{modal-UK8xRSnh.js → modal-qYgTMkYb.js} +1 -1
- package/web/assets/{project-launch.store-B5nhdGdh.js → project-launch.store-y7JBMNiE.js} +1 -1
- package/web/assets/{project.store-Bm2JWNbd.js → project.store-DGJr0Qr9.js} +1 -1
- package/web/assets/{scenario.store-88w6P6s2.js → scenario.store-YWHL2_wu.js} +3 -3
- package/web/assets/{service.store-XS50iGKo.js → service.store-5jWsJxPx.js} +1 -1
- package/web/assets/{settings-scope-CKPq86HZ.js → settings-scope-Bv7QGPrt.js} +1 -1
- package/web/assets/{settings.store-BCJ0LJQA.js → settings.store-bEsB7oKb.js} +1 -1
- package/web/assets/{src-DBipSD56.js → src-C-D_y7ou.js} +1 -1
- package/web/assets/{src-Cgix2VOi.js → src-Dnwk6NpV.js} +2 -2
- package/web/assets/{src-BDZKZr9n.css → src-g1DRo8xZ.css} +1 -1
- package/web/assets/{trpc-CfuFkKgp.js → trpc-Drabzwpd.js} +3 -3
- package/web/assets/{useCloudOrg-J8DAnocE.js → useCloudOrg-BAQEtFPl.js} +1 -1
- package/web/assets/{useEnvVarAutocomplete-C6zOXYqi.js → useEnvVarAutocomplete-BqGTODUv.js} +1 -1
- package/web/assets/{useProjectCatalog-BB5IR6o8.js → useProjectCatalog-yocjIKt6.js} +1 -1
- package/web/assets/{useProjectCloudOrg-BnlKts7x.js → useProjectCloudOrg-CEN9-_OA.js} +1 -1
- package/web/assets/{useServiceUrlHost-hKbKtPMu.js → useServiceUrlHost-BuoPEXZX.js} +1 -1
- package/web/assets/{vue-draggable-plus-D5ni4jMk.js → vue-draggable-plus-DX4rTBiu.js} +1 -1
- package/web/index.html +17 -17
- package/agent-manager-THV3LJOF.js +0 -11
- package/chunk-ONVAH65J.js.map +0 -1
- package/settings-manager-QVQRJO6V.js +0 -10
- package/src-IODFUZ7A.js.map +0 -1
- package/web/assets/SettingsPage-BSSHxxjM.js +0 -2086
- package/web/assets/SettingsPage-W8YszhUL.css +0 -1
- package/web/assets/i18n-8FjeJaKW.js +0 -1
- package/web/assets/index-teBqdnuv.css +0 -1
- /package/{agent-manager-THV3LJOF.js.map → agent-manager-R7BPQO6D.js.map} +0 -0
- /package/{chat-XYCHMTFE.js.map → chat-LPML22J5.js.map} +0 -0
- /package/{chunk-NCO4OOYT.js.map → chunk-4LJQIQMN.js.map} +0 -0
- /package/{chunk-QF56Z6LI.js.map → chunk-KV4GJKXU.js.map} +0 -0
- /package/{chunk-FE53XX4M.js.map → chunk-NPMSBYXC.js.map} +0 -0
- /package/{companion-HBYSY2D5.js.map → companion-W3UMZWGH.js.map} +0 -0
- /package/{install-gate-JAEK4BDZ.js.map → install-gate-PVMGJDWE.js.map} +0 -0
- /package/{settings-manager-QVQRJO6V.js.map → settings-manager-XPLBO35P.js.map} +0 -0
- /package/{vault-resolver-PN3TYCLJ.js.map → vault-resolver-E7KPKA5C.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,10 +65,11 @@ 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 {
|
|
70
74
|
CONVERSATIONS_DIRNAME,
|
|
71
75
|
LocalProjectRefusedError,
|
|
@@ -83,13 +87,14 @@ import {
|
|
|
83
87
|
resolveAgentCwd,
|
|
84
88
|
resolveConversationCwd,
|
|
85
89
|
resolveWorkspaceCwd,
|
|
90
|
+
setMcpClientCheck,
|
|
86
91
|
signJwt,
|
|
87
92
|
startClaudeTranscriptWatcher,
|
|
88
93
|
startCodexTranscriptWatcher,
|
|
89
94
|
stopClaudeTranscriptWatcher,
|
|
90
95
|
stopCodexTranscriptWatcher,
|
|
91
96
|
verifyJwt
|
|
92
|
-
} from "./chunk-
|
|
97
|
+
} from "./chunk-UD42LACH.js";
|
|
93
98
|
import {
|
|
94
99
|
agentManager,
|
|
95
100
|
agentSpawner,
|
|
@@ -128,10 +133,11 @@ import {
|
|
|
128
133
|
stableEncryptValue,
|
|
129
134
|
traefikManager,
|
|
130
135
|
unregisterInstance
|
|
131
|
-
} from "./chunk-
|
|
136
|
+
} from "./chunk-KV4GJKXU.js";
|
|
132
137
|
import {
|
|
133
138
|
decryptValueWith,
|
|
134
139
|
encryptValueWith,
|
|
140
|
+
isCliAvailable,
|
|
135
141
|
resolveCliInvocation,
|
|
136
142
|
whichCommand
|
|
137
143
|
} from "./chunk-OZBSOSAS.js";
|
|
@@ -140,7 +146,7 @@ import {
|
|
|
140
146
|
renameWithRetry,
|
|
141
147
|
resolveConversationRunDir,
|
|
142
148
|
sweepStaleTempFiles
|
|
143
|
-
} from "./chunk-
|
|
149
|
+
} from "./chunk-4LJQIQMN.js";
|
|
144
150
|
import "./chunk-MD3VW4HQ.js";
|
|
145
151
|
import {
|
|
146
152
|
allVaultRoots,
|
|
@@ -148,6 +154,7 @@ import {
|
|
|
148
154
|
env,
|
|
149
155
|
forgetOrgLogo,
|
|
150
156
|
keyRoot,
|
|
157
|
+
keyedFilePath,
|
|
151
158
|
legacyMachineFilePath,
|
|
152
159
|
legacyVaultRoot,
|
|
153
160
|
listCloudVaults,
|
|
@@ -167,7 +174,7 @@ import {
|
|
|
167
174
|
rememberOrgName,
|
|
168
175
|
stampOrgCloud,
|
|
169
176
|
vaultFor
|
|
170
|
-
} from "./chunk-
|
|
177
|
+
} from "./chunk-VPSQJOSN.js";
|
|
171
178
|
import {
|
|
172
179
|
AGENT_LONG_FETCH_TIMEOUT,
|
|
173
180
|
AGENT_STOP_FETCH_TIMEOUT,
|
|
@@ -211,7 +218,7 @@ import {
|
|
|
211
218
|
import express from "express";
|
|
212
219
|
import cors from "cors";
|
|
213
220
|
import { createServer } from "http";
|
|
214
|
-
import { join as
|
|
221
|
+
import { join as join18, resolve as resolve4 } from "path";
|
|
215
222
|
import { WebSocketServer } from "ws";
|
|
216
223
|
import { createExpressMiddleware } from "@trpc/server/adapters/express";
|
|
217
224
|
import { applyWSSHandler } from "@trpc/server/adapters/ws";
|
|
@@ -226,7 +233,16 @@ import { z } from "zod";
|
|
|
226
233
|
import { execFile } from "child_process";
|
|
227
234
|
import { promisify } from "util";
|
|
228
235
|
import { existsSync, mkdtempSync, readFileSync, rmdirSync } from "fs";
|
|
229
|
-
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
|
|
230
246
|
var execFileAsync = promisify(execFile);
|
|
231
247
|
var PACKAGE_NAME = "@runeya/runeya";
|
|
232
248
|
var INSTALL_TIMEOUT_MS = 10 * 6e4;
|
|
@@ -332,10 +348,6 @@ function installPrefix(globalPrefix, platform = process.platform) {
|
|
|
332
348
|
const modules = platform === "win32" ? join(globalPrefix, "node_modules") : join(globalPrefix, "lib", "node_modules");
|
|
333
349
|
return isWritable(modules) ? globalPrefix : userPrefix();
|
|
334
350
|
}
|
|
335
|
-
function isInside(dir, path) {
|
|
336
|
-
const rel = relative(dir, path);
|
|
337
|
-
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
338
|
-
}
|
|
339
351
|
function updatePrefix(globalPrefix, runningEntry) {
|
|
340
352
|
if (isInside(userPrefix(), runningEntry)) return userPrefix();
|
|
341
353
|
if (!globalPrefix) return null;
|
|
@@ -642,6 +654,23 @@ function onCurrentProjectChanged(listener) {
|
|
|
642
654
|
return () => listeners.delete(listener);
|
|
643
655
|
}
|
|
644
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
|
+
|
|
645
674
|
// ../server/src/services/org-keys.ts
|
|
646
675
|
import { mkdir } from "fs/promises";
|
|
647
676
|
|
|
@@ -950,6 +979,13 @@ async function assertReadable(orgId) {
|
|
|
950
979
|
throw err;
|
|
951
980
|
}
|
|
952
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
|
+
}
|
|
953
989
|
var projectRouter = router({
|
|
954
990
|
list: protectedProcedure.query(async () => {
|
|
955
991
|
return projectStore.list();
|
|
@@ -1231,12 +1267,16 @@ var projectRouter = router({
|
|
|
1231
1267
|
* Le dossier de chaque projet sur cette machine, par clé `org:projet`
|
|
1232
1268
|
* (`local:<id>` pour un projet local, dont le dossier contient son `.runeya`).
|
|
1233
1269
|
*/
|
|
1234
|
-
localPaths: protectedProcedure.query(
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
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;
|
|
1240
1280
|
}),
|
|
1241
1281
|
getLocalPath: protectedProcedure.input(z5.object({
|
|
1242
1282
|
id: z5.string().min(1).max(128),
|
|
@@ -1334,8 +1374,8 @@ var projectRouter = router({
|
|
|
1334
1374
|
touched.clear();
|
|
1335
1375
|
yield { collections };
|
|
1336
1376
|
} else {
|
|
1337
|
-
await new Promise((
|
|
1338
|
-
wake =
|
|
1377
|
+
await new Promise((resolve5) => {
|
|
1378
|
+
wake = resolve5;
|
|
1339
1379
|
});
|
|
1340
1380
|
}
|
|
1341
1381
|
}
|
|
@@ -1483,13 +1523,13 @@ var agentRouter = router({
|
|
|
1483
1523
|
yield { agentId: agent.config.id, status: agent.status };
|
|
1484
1524
|
}
|
|
1485
1525
|
const queue = [];
|
|
1486
|
-
let
|
|
1526
|
+
let resolve5 = null;
|
|
1487
1527
|
const handler = (event) => {
|
|
1488
1528
|
if (queue.length >= SUBSCRIPTION_QUEUE_MAX) queue.shift();
|
|
1489
1529
|
queue.push(event);
|
|
1490
|
-
if (
|
|
1491
|
-
|
|
1492
|
-
|
|
1530
|
+
if (resolve5) {
|
|
1531
|
+
resolve5();
|
|
1532
|
+
resolve5 = null;
|
|
1493
1533
|
}
|
|
1494
1534
|
};
|
|
1495
1535
|
agentManager.on("status", handler);
|
|
@@ -1499,7 +1539,7 @@ var agentRouter = router({
|
|
|
1499
1539
|
yield queue.shift();
|
|
1500
1540
|
} else {
|
|
1501
1541
|
await new Promise((r) => {
|
|
1502
|
-
|
|
1542
|
+
resolve5 = r;
|
|
1503
1543
|
});
|
|
1504
1544
|
}
|
|
1505
1545
|
}
|
|
@@ -1618,14 +1658,14 @@ async function listAllProcesses() {
|
|
|
1618
1658
|
var SUBSCRIPTION_QUEUE_MAX2 = LOG_BUFFER_SIZE;
|
|
1619
1659
|
async function* forwardEvents(eventName, filter) {
|
|
1620
1660
|
const queue = [];
|
|
1621
|
-
let
|
|
1661
|
+
let resolve5 = null;
|
|
1622
1662
|
const handler = (data) => {
|
|
1623
1663
|
if (filter && !filter(data)) return;
|
|
1624
1664
|
if (queue.length >= SUBSCRIPTION_QUEUE_MAX2) queue.shift();
|
|
1625
1665
|
queue.push(data);
|
|
1626
|
-
if (
|
|
1627
|
-
|
|
1628
|
-
|
|
1666
|
+
if (resolve5) {
|
|
1667
|
+
resolve5();
|
|
1668
|
+
resolve5 = null;
|
|
1629
1669
|
}
|
|
1630
1670
|
};
|
|
1631
1671
|
agentManager.on(eventName, handler);
|
|
@@ -1635,7 +1675,7 @@ async function* forwardEvents(eventName, filter) {
|
|
|
1635
1675
|
yield queue.shift();
|
|
1636
1676
|
} else {
|
|
1637
1677
|
await new Promise((r) => {
|
|
1638
|
-
|
|
1678
|
+
resolve5 = r;
|
|
1639
1679
|
});
|
|
1640
1680
|
}
|
|
1641
1681
|
}
|
|
@@ -1645,7 +1685,7 @@ async function* forwardEvents(eventName, filter) {
|
|
|
1645
1685
|
}
|
|
1646
1686
|
async function* forwardLogBatches(agentId, processId) {
|
|
1647
1687
|
const queue = [];
|
|
1648
|
-
let
|
|
1688
|
+
let resolve5 = null;
|
|
1649
1689
|
const handler = (data) => {
|
|
1650
1690
|
if (data.agentId !== agentId) return;
|
|
1651
1691
|
const batch = data.batch;
|
|
@@ -1654,9 +1694,9 @@ async function* forwardLogBatches(agentId, processId) {
|
|
|
1654
1694
|
if (filtered.length === 0) return;
|
|
1655
1695
|
if (queue.length >= SUBSCRIPTION_QUEUE_MAX2) queue.shift();
|
|
1656
1696
|
queue.push(filtered);
|
|
1657
|
-
if (
|
|
1658
|
-
|
|
1659
|
-
|
|
1697
|
+
if (resolve5) {
|
|
1698
|
+
resolve5();
|
|
1699
|
+
resolve5 = null;
|
|
1660
1700
|
}
|
|
1661
1701
|
};
|
|
1662
1702
|
agentManager.on("process:log", handler);
|
|
@@ -1666,7 +1706,7 @@ async function* forwardLogBatches(agentId, processId) {
|
|
|
1666
1706
|
yield queue.shift();
|
|
1667
1707
|
} else {
|
|
1668
1708
|
await new Promise((r) => {
|
|
1669
|
-
|
|
1709
|
+
resolve5 = r;
|
|
1670
1710
|
});
|
|
1671
1711
|
}
|
|
1672
1712
|
}
|
|
@@ -2688,8 +2728,8 @@ var settingsRouter = router({
|
|
|
2688
2728
|
pending = false;
|
|
2689
2729
|
yield listWithOrigin(await logParserStore.list(vault), vault);
|
|
2690
2730
|
} else {
|
|
2691
|
-
await new Promise((
|
|
2692
|
-
wake =
|
|
2731
|
+
await new Promise((resolve5) => {
|
|
2732
|
+
wake = resolve5;
|
|
2693
2733
|
});
|
|
2694
2734
|
}
|
|
2695
2735
|
}
|
|
@@ -3046,7 +3086,7 @@ var settingsRouter = router({
|
|
|
3046
3086
|
}
|
|
3047
3087
|
} catch {
|
|
3048
3088
|
}
|
|
3049
|
-
await new Promise((
|
|
3089
|
+
await new Promise((resolve5) => setTimeout(resolve5, 500));
|
|
3050
3090
|
}
|
|
3051
3091
|
}),
|
|
3052
3092
|
/**
|
|
@@ -3233,7 +3273,7 @@ import { z as z13 } from "zod";
|
|
|
3233
3273
|
import { TRPCError as TRPCError9 } from "@trpc/server";
|
|
3234
3274
|
import { execFile as execFile3, spawn as spawn2 } from "child_process";
|
|
3235
3275
|
import { stat as stat2 } from "fs/promises";
|
|
3236
|
-
import { resolve, join as join4 } from "path";
|
|
3276
|
+
import { resolve as resolve2, join as join4 } from "path";
|
|
3237
3277
|
import { homedir as homedir2 } from "os";
|
|
3238
3278
|
|
|
3239
3279
|
// ../server/src/services/user-config-manager.ts
|
|
@@ -3328,9 +3368,9 @@ async function getServiceCwd(serviceId) {
|
|
|
3328
3368
|
return resolveCwd(await resolveWorkspaceCwd({ serviceId }));
|
|
3329
3369
|
}
|
|
3330
3370
|
async function isCommandAvailable(command) {
|
|
3331
|
-
return new Promise((
|
|
3371
|
+
return new Promise((resolve5) => {
|
|
3332
3372
|
execFile3(whichCommand(), [command], { timeout: 5e3, windowsHide: true }, (err) => {
|
|
3333
|
-
|
|
3373
|
+
resolve5(!err);
|
|
3334
3374
|
});
|
|
3335
3375
|
});
|
|
3336
3376
|
}
|
|
@@ -3401,7 +3441,7 @@ var editorRouter = router({
|
|
|
3401
3441
|
throw new TRPCError9({ code: "BAD_REQUEST", message: `Unknown editor: ${input.editorKey}` });
|
|
3402
3442
|
}
|
|
3403
3443
|
const expanded = input.path.replace(/^~(\/|$)/, `${process.env["HOME"] ?? "/tmp"}$1`).replace(/\$\{(\w+)\}|\$(\w+)/g, (_, a, b) => process.env[a || b] ?? "");
|
|
3404
|
-
const resolvedPath =
|
|
3444
|
+
const resolvedPath = resolve2(expanded || ".");
|
|
3405
3445
|
try {
|
|
3406
3446
|
await stat2(resolvedPath);
|
|
3407
3447
|
} catch (err) {
|
|
@@ -3708,7 +3748,7 @@ Rules:
|
|
|
3708
3748
|
onStatusChange: protectedProcedure.input(z15.object({ ...gitTargetShape })).subscription(async function* ({ input }) {
|
|
3709
3749
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
3710
3750
|
const queue = [];
|
|
3711
|
-
let
|
|
3751
|
+
let resolve5 = null;
|
|
3712
3752
|
let done = false;
|
|
3713
3753
|
let unsubscribe;
|
|
3714
3754
|
try {
|
|
@@ -3718,16 +3758,16 @@ Rules:
|
|
|
3718
3758
|
{ cwd },
|
|
3719
3759
|
(data) => {
|
|
3720
3760
|
queue.push(data);
|
|
3721
|
-
if (
|
|
3722
|
-
|
|
3723
|
-
|
|
3761
|
+
if (resolve5) {
|
|
3762
|
+
resolve5();
|
|
3763
|
+
resolve5 = null;
|
|
3724
3764
|
}
|
|
3725
3765
|
},
|
|
3726
3766
|
() => {
|
|
3727
3767
|
done = true;
|
|
3728
|
-
if (
|
|
3729
|
-
|
|
3730
|
-
|
|
3768
|
+
if (resolve5) {
|
|
3769
|
+
resolve5();
|
|
3770
|
+
resolve5 = null;
|
|
3731
3771
|
}
|
|
3732
3772
|
}
|
|
3733
3773
|
);
|
|
@@ -3743,7 +3783,7 @@ Rules:
|
|
|
3743
3783
|
yield queue.shift();
|
|
3744
3784
|
} else {
|
|
3745
3785
|
await new Promise((r) => {
|
|
3746
|
-
|
|
3786
|
+
resolve5 = r;
|
|
3747
3787
|
});
|
|
3748
3788
|
}
|
|
3749
3789
|
}
|
|
@@ -4091,21 +4131,21 @@ async function* tapBus(conversationId, signal) {
|
|
|
4091
4131
|
}
|
|
4092
4132
|
if (bus.done) return;
|
|
4093
4133
|
if (signal.aborted) return;
|
|
4094
|
-
await new Promise((
|
|
4134
|
+
await new Promise((resolve5) => {
|
|
4095
4135
|
const onAbort = () => {
|
|
4096
4136
|
bus.listeners.delete(notify);
|
|
4097
|
-
|
|
4137
|
+
resolve5();
|
|
4098
4138
|
};
|
|
4099
4139
|
const notify = () => {
|
|
4100
4140
|
signal.removeEventListener("abort", onAbort);
|
|
4101
|
-
|
|
4141
|
+
resolve5();
|
|
4102
4142
|
};
|
|
4103
4143
|
bus.listeners.add(notify);
|
|
4104
4144
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
4105
4145
|
if (cursor < bus.baseIndex + bus.buffer.length || bus.done || signal.aborted) {
|
|
4106
4146
|
bus.listeners.delete(notify);
|
|
4107
4147
|
signal.removeEventListener("abort", onAbort);
|
|
4108
|
-
|
|
4148
|
+
resolve5();
|
|
4109
4149
|
}
|
|
4110
4150
|
});
|
|
4111
4151
|
}
|
|
@@ -4615,9 +4655,9 @@ async function runNodeCall(conversationId, node, systemContext, userMessage, run
|
|
|
4615
4655
|
publish2({ type: "node_waiting_input", nodeId: node.id, question: result.tokenMessage });
|
|
4616
4656
|
await readme?.(`- \u2753 [${formatTs()}] **${node.label}** \u2014 waiting for user input
|
|
4617
4657
|
`);
|
|
4618
|
-
const answer = await new Promise((
|
|
4658
|
+
const answer = await new Promise((resolve5, reject) => {
|
|
4619
4659
|
const key = pendingNodeKey(conversationId, node.id);
|
|
4620
|
-
pendingNodeInputs.set(key,
|
|
4660
|
+
pendingNodeInputs.set(key, resolve5);
|
|
4621
4661
|
signal.addEventListener("abort", () => {
|
|
4622
4662
|
pendingNodeInputs.delete(key);
|
|
4623
4663
|
reject(new Error("Aborted"));
|
|
@@ -5022,9 +5062,9 @@ ${outputs.get(node.id) ?? ""}`.trim();
|
|
|
5022
5062
|
publish2({ type: "node_waiting_input", nodeId: node.id, question });
|
|
5023
5063
|
await readme(`- \u2753 [${ts()}] **${node.label || node.id}** \u2014 waiting for user input
|
|
5024
5064
|
`);
|
|
5025
|
-
const answer = await new Promise((
|
|
5065
|
+
const answer = await new Promise((resolve5, reject) => {
|
|
5026
5066
|
const key = pendingNodeKey(conversationId, node.id);
|
|
5027
|
-
pendingNodeInputs.set(key,
|
|
5067
|
+
pendingNodeInputs.set(key, resolve5);
|
|
5028
5068
|
signal.addEventListener("abort", () => {
|
|
5029
5069
|
pendingNodeInputs.delete(key);
|
|
5030
5070
|
reject(new Error("Aborted"));
|
|
@@ -5577,21 +5617,21 @@ async function* tapJob(job, signal) {
|
|
|
5577
5617
|
yield evt;
|
|
5578
5618
|
}
|
|
5579
5619
|
if (job.done || signal.aborted) return;
|
|
5580
|
-
await new Promise((
|
|
5620
|
+
await new Promise((resolve5) => {
|
|
5581
5621
|
const onAbort = () => {
|
|
5582
5622
|
job.listeners.delete(notify);
|
|
5583
|
-
|
|
5623
|
+
resolve5();
|
|
5584
5624
|
};
|
|
5585
5625
|
const notify = () => {
|
|
5586
5626
|
signal.removeEventListener("abort", onAbort);
|
|
5587
|
-
|
|
5627
|
+
resolve5();
|
|
5588
5628
|
};
|
|
5589
5629
|
job.listeners.add(notify);
|
|
5590
5630
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
5591
5631
|
if (cursor < job.baseIndex + job.buffer.length || job.done || signal.aborted) {
|
|
5592
5632
|
job.listeners.delete(notify);
|
|
5593
5633
|
signal.removeEventListener("abort", onAbort);
|
|
5594
|
-
|
|
5634
|
+
resolve5();
|
|
5595
5635
|
}
|
|
5596
5636
|
});
|
|
5597
5637
|
}
|
|
@@ -5743,9 +5783,9 @@ var diagnosticsRouter = router({
|
|
|
5743
5783
|
try {
|
|
5744
5784
|
while (!signal?.aborted) {
|
|
5745
5785
|
if (queue.length === 0) {
|
|
5746
|
-
await new Promise((
|
|
5747
|
-
wake =
|
|
5748
|
-
signal?.addEventListener("abort", () =>
|
|
5786
|
+
await new Promise((resolve5) => {
|
|
5787
|
+
wake = resolve5;
|
|
5788
|
+
signal?.addEventListener("abort", () => resolve5(), { once: true });
|
|
5749
5789
|
});
|
|
5750
5790
|
wake = null;
|
|
5751
5791
|
continue;
|
|
@@ -5817,11 +5857,395 @@ var diagnosticsRouter = router({
|
|
|
5817
5857
|
})
|
|
5818
5858
|
});
|
|
5819
5859
|
|
|
5820
|
-
// ../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";
|
|
5821
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";
|
|
5822
6246
|
|
|
5823
6247
|
// ../server/src/services/kanban-queue.ts
|
|
5824
|
-
import { randomUUID as
|
|
6248
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
5825
6249
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
5826
6250
|
var KanbanQueueService = class extends EventEmitter2 {
|
|
5827
6251
|
items = [];
|
|
@@ -5849,7 +6273,7 @@ var KanbanQueueService = class extends EventEmitter2 {
|
|
|
5849
6273
|
return { id: alreadyRunning.id, addedAt: alreadyRunning.startedAt, ...item };
|
|
5850
6274
|
}
|
|
5851
6275
|
const queueItem = {
|
|
5852
|
-
id:
|
|
6276
|
+
id: randomUUID6(),
|
|
5853
6277
|
...item,
|
|
5854
6278
|
addedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5855
6279
|
};
|
|
@@ -5877,9 +6301,9 @@ var KanbanQueueService = class extends EventEmitter2 {
|
|
|
5877
6301
|
}
|
|
5878
6302
|
const runIdx = this.running.findIndex((r) => r.id === itemId);
|
|
5879
6303
|
if (runIdx >= 0) {
|
|
5880
|
-
const
|
|
5881
|
-
abortScenario(
|
|
5882
|
-
activeSessionRegistry.kill(
|
|
6304
|
+
const run2 = this.running[runIdx];
|
|
6305
|
+
abortScenario(run2.conversationId);
|
|
6306
|
+
activeSessionRegistry.kill(run2.conversationId);
|
|
5883
6307
|
this.running.splice(runIdx, 1);
|
|
5884
6308
|
this.emit("changed");
|
|
5885
6309
|
this.tick();
|
|
@@ -5889,9 +6313,9 @@ var KanbanQueueService = class extends EventEmitter2 {
|
|
|
5889
6313
|
}
|
|
5890
6314
|
stopAll() {
|
|
5891
6315
|
this.items = [];
|
|
5892
|
-
for (const
|
|
5893
|
-
abortScenario(
|
|
5894
|
-
activeSessionRegistry.kill(
|
|
6316
|
+
for (const run2 of this.running) {
|
|
6317
|
+
abortScenario(run2.conversationId);
|
|
6318
|
+
activeSessionRegistry.kill(run2.conversationId);
|
|
5895
6319
|
}
|
|
5896
6320
|
this.running = [];
|
|
5897
6321
|
this.emit("changed");
|
|
@@ -6010,50 +6434,50 @@ var kanbanQueue = new KanbanQueueService();
|
|
|
6010
6434
|
// ../server/src/trpc/routers/kanban-queue.ts
|
|
6011
6435
|
var kanbanQueueRouter = router({
|
|
6012
6436
|
getState: protectedProcedure.query(() => kanbanQueue.getState()),
|
|
6013
|
-
add: protectedProcedure.input(
|
|
6014
|
-
boardId:
|
|
6015
|
-
cardId:
|
|
6016
|
-
runner:
|
|
6017
|
-
model:
|
|
6018
|
-
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(),
|
|
6019
6443
|
// Le projet ouvert quand la carte a été mise en file : la conversation
|
|
6020
6444
|
// qui en naîtra lui appartient, plutôt que de rester orpheline et de
|
|
6021
6445
|
// s'afficher dans tous les projets.
|
|
6022
|
-
projectId:
|
|
6023
|
-
orgId:
|
|
6446
|
+
projectId: z22.string().max(128).optional(),
|
|
6447
|
+
orgId: z22.string().max(128).nullable().optional()
|
|
6024
6448
|
})).mutation(({ input }) => kanbanQueue.add(input)),
|
|
6025
|
-
addColumn: protectedProcedure.input(
|
|
6026
|
-
boardId:
|
|
6027
|
-
columnId:
|
|
6449
|
+
addColumn: protectedProcedure.input(z22.object({
|
|
6450
|
+
boardId: z22.string().max(128),
|
|
6451
|
+
columnId: z22.string().max(128),
|
|
6028
6452
|
// Les cartes visibles côté écran : une colonne filtrée n'envoie qu'elles.
|
|
6029
|
-
cardIds:
|
|
6030
|
-
runner:
|
|
6031
|
-
model:
|
|
6032
|
-
scenarioId:
|
|
6033
|
-
projectId:
|
|
6034
|
-
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()
|
|
6035
6459
|
})).mutation(async ({ input }) => {
|
|
6036
6460
|
const { boardId, columnId, ...options } = input;
|
|
6037
6461
|
return kanbanQueue.addColumn(boardId, columnId, options);
|
|
6038
6462
|
}),
|
|
6039
|
-
remove: protectedProcedure.input(
|
|
6463
|
+
remove: protectedProcedure.input(z22.object({ itemId: z22.string().max(128) })).mutation(({ input }) => ({ removed: kanbanQueue.remove(input.itemId) })),
|
|
6040
6464
|
stopAll: protectedProcedure.mutation(() => {
|
|
6041
6465
|
kanbanQueue.stopAll();
|
|
6042
6466
|
return { ok: true };
|
|
6043
6467
|
}),
|
|
6044
|
-
setMaxConcurrent: protectedProcedure.input(
|
|
6468
|
+
setMaxConcurrent: protectedProcedure.input(z22.object({ max: z22.number().int().min(1).max(20) })).mutation(({ input }) => {
|
|
6045
6469
|
kanbanQueue.setMaxConcurrent(input.max);
|
|
6046
6470
|
return kanbanQueue.getState();
|
|
6047
6471
|
}),
|
|
6048
6472
|
onChanged: protectedProcedure.subscription(async function* () {
|
|
6049
6473
|
const queue = [];
|
|
6050
|
-
let
|
|
6474
|
+
let resolve5 = null;
|
|
6051
6475
|
const handler = () => {
|
|
6052
6476
|
if (queue.length >= 50) queue.shift();
|
|
6053
6477
|
queue.push(kanbanQueue.getState());
|
|
6054
|
-
if (
|
|
6055
|
-
|
|
6056
|
-
|
|
6478
|
+
if (resolve5) {
|
|
6479
|
+
resolve5();
|
|
6480
|
+
resolve5 = null;
|
|
6057
6481
|
}
|
|
6058
6482
|
};
|
|
6059
6483
|
kanbanQueue.on("changed", handler);
|
|
@@ -6064,7 +6488,7 @@ var kanbanQueueRouter = router({
|
|
|
6064
6488
|
yield queue.shift();
|
|
6065
6489
|
} else {
|
|
6066
6490
|
await new Promise((r) => {
|
|
6067
|
-
|
|
6491
|
+
resolve5 = r;
|
|
6068
6492
|
});
|
|
6069
6493
|
}
|
|
6070
6494
|
}
|
|
@@ -6075,12 +6499,12 @@ var kanbanQueueRouter = router({
|
|
|
6075
6499
|
});
|
|
6076
6500
|
|
|
6077
6501
|
// ../server/src/trpc/routers/kanban.ts
|
|
6078
|
-
import { mkdir as
|
|
6079
|
-
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";
|
|
6080
6504
|
import { tmpdir } from "os";
|
|
6081
|
-
import { join as
|
|
6082
|
-
import { z as
|
|
6083
|
-
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";
|
|
6084
6508
|
function summarizeCard(card, boardId, columnTitle) {
|
|
6085
6509
|
return {
|
|
6086
6510
|
boardId,
|
|
@@ -6121,14 +6545,14 @@ function safeDecode(value) {
|
|
|
6121
6545
|
}
|
|
6122
6546
|
var MAX_ATTACHMENT_BYTES = 55 * 1024 * 1024;
|
|
6123
6547
|
function attachmentCacheDir() {
|
|
6124
|
-
return
|
|
6548
|
+
return join8(tmpdir(), "runeya-kanban-attachments");
|
|
6125
6549
|
}
|
|
6126
6550
|
var ATTACHMENT_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
6127
6551
|
async function purgeStaleAttachments(dir, ttlMs = ATTACHMENT_CACHE_TTL_MS) {
|
|
6128
6552
|
try {
|
|
6129
6553
|
const cutoff = Date.now() - ttlMs;
|
|
6130
6554
|
for (const name of await readdir(dir)) {
|
|
6131
|
-
const path =
|
|
6555
|
+
const path = join8(dir, name);
|
|
6132
6556
|
const info = await stat3(path).catch(() => null);
|
|
6133
6557
|
if (info && info.mtimeMs < cutoff) await rm(path, { force: true });
|
|
6134
6558
|
}
|
|
@@ -6136,14 +6560,14 @@ async function purgeStaleAttachments(dir, ttlMs = ATTACHMENT_CACHE_TTL_MS) {
|
|
|
6136
6560
|
}
|
|
6137
6561
|
}
|
|
6138
6562
|
function attachmentCacheDirs() {
|
|
6139
|
-
return [attachmentCacheDir(),
|
|
6563
|
+
return [attachmentCacheDir(), join8(machineRoot(), "kanban-attachments")];
|
|
6140
6564
|
}
|
|
6141
6565
|
async function storeAttachment(id, name, body, dirs = attachmentCacheDirs()) {
|
|
6142
6566
|
const refusals = [];
|
|
6143
6567
|
for (const dir of dirs) {
|
|
6144
6568
|
try {
|
|
6145
|
-
await
|
|
6146
|
-
const path =
|
|
6569
|
+
await mkdir6(dir, { recursive: true });
|
|
6570
|
+
const path = join8(dir, safeLocalName(id, name));
|
|
6147
6571
|
await writeFile4(path, body);
|
|
6148
6572
|
void purgeStaleAttachments(dir);
|
|
6149
6573
|
return path;
|
|
@@ -6151,13 +6575,13 @@ async function storeAttachment(id, name, body, dirs = attachmentCacheDirs()) {
|
|
|
6151
6575
|
refusals.push(`${dir} (${err instanceof Error ? err.message : String(err)})`);
|
|
6152
6576
|
}
|
|
6153
6577
|
}
|
|
6154
|
-
throw new
|
|
6578
|
+
throw new TRPCError19({
|
|
6155
6579
|
code: "INTERNAL_SERVER_ERROR",
|
|
6156
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.`
|
|
6157
6581
|
});
|
|
6158
6582
|
}
|
|
6159
6583
|
function safeLocalName(id, name) {
|
|
6160
|
-
const base =
|
|
6584
|
+
const base = createHash2("sha256").update(id).digest("hex").slice(0, 32);
|
|
6161
6585
|
const ext = /\.([A-Za-z0-9]{1,12})$/.exec(name)?.[1];
|
|
6162
6586
|
return ext ? `${base}.${ext.toLowerCase()}` : base;
|
|
6163
6587
|
}
|
|
@@ -6172,9 +6596,9 @@ var kanbanRouter = router({
|
|
|
6172
6596
|
* card graph, so listing them in full would be megabytes to answer "which
|
|
6173
6597
|
* boards exist?". Use `getBoard` for the cards of one.
|
|
6174
6598
|
*/
|
|
6175
|
-
listBoards: protectedProcedure.input(
|
|
6176
|
-
includeArchived:
|
|
6177
|
-
organizationId:
|
|
6599
|
+
listBoards: protectedProcedure.input(z23.object({
|
|
6600
|
+
includeArchived: z23.boolean().optional(),
|
|
6601
|
+
organizationId: z23.string().max(128).optional()
|
|
6178
6602
|
}).optional()).query(async ({ input }) => {
|
|
6179
6603
|
const { organizationId, boards } = await kanbanStore.list(
|
|
6180
6604
|
input?.includeArchived ?? false,
|
|
@@ -6200,7 +6624,7 @@ var kanbanRouter = router({
|
|
|
6200
6624
|
};
|
|
6201
6625
|
}),
|
|
6202
6626
|
/** One board: its columns, and the cards in each. */
|
|
6203
|
-
getBoard: protectedProcedure.input(
|
|
6627
|
+
getBoard: protectedProcedure.input(z23.object({ boardId: z23.string().max(128) })).query(async ({ input }) => {
|
|
6204
6628
|
const board = await kanbanStore.get(input.boardId);
|
|
6205
6629
|
if (!board) return { board: null };
|
|
6206
6630
|
const { links } = await kanbanStore.cardLinks(input.boardId);
|
|
@@ -6239,12 +6663,12 @@ var kanbanRouter = router({
|
|
|
6239
6663
|
* organization — or one board when `boardId` is given. Archived boards are
|
|
6240
6664
|
* left out: they are what the team decided to stop looking at.
|
|
6241
6665
|
*/
|
|
6242
|
-
searchCards: protectedProcedure.input(
|
|
6243
|
-
query:
|
|
6244
|
-
createdBy:
|
|
6245
|
-
boardId:
|
|
6246
|
-
limit:
|
|
6247
|
-
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()
|
|
6248
6672
|
}).refine((i) => !!i.query?.trim() || !!i.createdBy?.trim(), { message: "query or createdBy is required." })).query(async ({ input }) => {
|
|
6249
6673
|
return kanbanStore.searchCards(input);
|
|
6250
6674
|
}),
|
|
@@ -6252,10 +6676,10 @@ var kanbanRouter = router({
|
|
|
6252
6676
|
* Les conversations de support liées à une carte, avec leur fil — ce que
|
|
6253
6677
|
* le client a écrit à l'équipe, séparé des commentaires de la carte.
|
|
6254
6678
|
*/
|
|
6255
|
-
getCardConversations: protectedProcedure.input(
|
|
6256
|
-
boardId:
|
|
6257
|
-
cardId:
|
|
6258
|
-
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()
|
|
6259
6683
|
})).query(async ({ input }) => {
|
|
6260
6684
|
const conversations = await kanbanStore.cardConversations(input.boardId, input.cardId);
|
|
6261
6685
|
const limit = input.limit ?? 100;
|
|
@@ -6268,13 +6692,13 @@ var kanbanRouter = router({
|
|
|
6268
6692
|
};
|
|
6269
6693
|
}),
|
|
6270
6694
|
/** Chercher une conversation chez les fournisseurs de l'organisation (lecture seule côté fournisseur). */
|
|
6271
|
-
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) })),
|
|
6272
6696
|
/** Lier une conversation trouvée à une carte. N'écrit rien chez le fournisseur. */
|
|
6273
|
-
linkConversation: protectedProcedure.input(
|
|
6274
|
-
boardId:
|
|
6275
|
-
cardId:
|
|
6276
|
-
connectionId:
|
|
6277
|
-
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)
|
|
6278
6702
|
})).mutation(async ({ input }) => ({ conversations: await kanbanStore.linkConversation(input) })),
|
|
6279
6703
|
/**
|
|
6280
6704
|
* Le fil de commentaires d'une carte, relu à la demande.
|
|
@@ -6285,10 +6709,10 @@ var kanbanRouter = router({
|
|
|
6285
6709
|
* qu'un `commentCount`, et rien n'en donnait le texte. Cette route est la
|
|
6286
6710
|
* relecture, pour une seule carte : c'est le journal qui pèse, pas le reste.
|
|
6287
6711
|
*/
|
|
6288
|
-
getCardComments: protectedProcedure.input(
|
|
6289
|
-
boardId:
|
|
6290
|
-
cardId:
|
|
6291
|
-
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()
|
|
6292
6716
|
})).query(async ({ input }) => {
|
|
6293
6717
|
const history = await kanbanStore.cardHistory(input.boardId, input.cardId);
|
|
6294
6718
|
const comments = history.filter((e) => e.type === "comment");
|
|
@@ -6317,18 +6741,18 @@ var kanbanRouter = router({
|
|
|
6317
6741
|
* `getBoard` en donne déjà l'essentiel carte par carte ; cette route sert
|
|
6318
6742
|
* quand c'est l'arbre lui-même qu'on veut, dates et auteurs compris.
|
|
6319
6743
|
*/
|
|
6320
|
-
getCardLinks: protectedProcedure.input(
|
|
6744
|
+
getCardLinks: protectedProcedure.input(z23.object({ boardId: z23.string().max(128) })).query(async ({ input }) => kanbanStore.cardLinks(input.boardId)),
|
|
6321
6745
|
/**
|
|
6322
6746
|
* Rattacher une carte à une carte parente, ou l'en détacher.
|
|
6323
6747
|
*
|
|
6324
6748
|
* Sans `parentCardId`, le lien est retiré. Le parent peut vivre sur un autre
|
|
6325
6749
|
* tableau : `parentBoardId` le désigne alors, sinon c'est celui de la carte.
|
|
6326
6750
|
*/
|
|
6327
|
-
setCardParent: protectedProcedure.input(
|
|
6328
|
-
boardId:
|
|
6329
|
-
cardId:
|
|
6330
|
-
parentCardId:
|
|
6331
|
-
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()
|
|
6332
6756
|
})).mutation(async ({ input }) => {
|
|
6333
6757
|
const ok = await kanbanStore.setCardParent(
|
|
6334
6758
|
input.boardId,
|
|
@@ -6336,7 +6760,7 @@ var kanbanRouter = router({
|
|
|
6336
6760
|
input.parentCardId ? { boardId: input.parentBoardId ?? input.boardId, cardId: input.parentCardId } : null
|
|
6337
6761
|
);
|
|
6338
6762
|
if (!ok) {
|
|
6339
|
-
throw new
|
|
6763
|
+
throw new TRPCError19({ code: "BAD_REQUEST", message: "The cloud refused the link (unknown card, or a cycle)." });
|
|
6340
6764
|
}
|
|
6341
6765
|
return { ok: true };
|
|
6342
6766
|
}),
|
|
@@ -6345,48 +6769,48 @@ var kanbanRouter = router({
|
|
|
6345
6769
|
* `boardId` est le tableau où on la pose (ou d'où on la retire) ; la carte
|
|
6346
6770
|
* reste sur son tableau d'origine.
|
|
6347
6771
|
*/
|
|
6348
|
-
getCardPlacements: protectedProcedure.input(
|
|
6349
|
-
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 }) => {
|
|
6350
6774
|
const res = await kanbanStore.placeCard(input.boardId, input.cardId, input.column);
|
|
6351
6775
|
if (!res.ok) {
|
|
6352
|
-
throw new
|
|
6776
|
+
throw new TRPCError19({
|
|
6353
6777
|
code: "BAD_REQUEST",
|
|
6354
6778
|
message: res.reason ?? "The cloud refused the placement (unknown card or column, archived card, or already on this board)."
|
|
6355
6779
|
});
|
|
6356
6780
|
}
|
|
6357
6781
|
return { ok: true };
|
|
6358
6782
|
}),
|
|
6359
|
-
unplaceCard: protectedProcedure.input(
|
|
6783
|
+
unplaceCard: protectedProcedure.input(z23.object({ boardId: z23.string().max(128), cardId: z23.string().max(128) })).mutation(async ({ input }) => {
|
|
6360
6784
|
if (!await kanbanStore.unplaceCard(input.boardId, input.cardId)) {
|
|
6361
|
-
throw new
|
|
6785
|
+
throw new TRPCError19({ code: "NOT_FOUND", message: "This card is not placed on this board." });
|
|
6362
6786
|
}
|
|
6363
6787
|
return { ok: true };
|
|
6364
6788
|
}),
|
|
6365
|
-
moveCard: protectedProcedure.input(
|
|
6366
|
-
boardId:
|
|
6367
|
-
cardId:
|
|
6368
|
-
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)
|
|
6369
6793
|
})).mutation(async ({ input }) => {
|
|
6370
6794
|
await executeKanbanCommands(input.boardId, input.cardId, [
|
|
6371
6795
|
{ type: "move_card", columnTitle: input.targetColumn }
|
|
6372
6796
|
]);
|
|
6373
6797
|
return { ok: true };
|
|
6374
6798
|
}),
|
|
6375
|
-
addComment: protectedProcedure.input(
|
|
6376
|
-
boardId:
|
|
6377
|
-
cardId:
|
|
6378
|
-
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)
|
|
6379
6803
|
})).mutation(async ({ input }) => {
|
|
6380
6804
|
await executeKanbanCommands(input.boardId, input.cardId, [
|
|
6381
6805
|
{ type: "add_comment", comment: input.comment }
|
|
6382
6806
|
]);
|
|
6383
6807
|
return { ok: true };
|
|
6384
6808
|
}),
|
|
6385
|
-
addCard: protectedProcedure.input(
|
|
6386
|
-
boardId:
|
|
6387
|
-
column:
|
|
6388
|
-
title:
|
|
6389
|
-
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()
|
|
6390
6814
|
})).mutation(async ({ input }) => {
|
|
6391
6815
|
await executeKanbanBoardCommands(input.boardId, [
|
|
6392
6816
|
{ type: "add_card", columnTitle: input.column, cardTitle: input.title, cardContent: input.content }
|
|
@@ -6399,11 +6823,34 @@ var kanbanRouter = router({
|
|
|
6399
6823
|
* `getBoard` rapatrie le tableau entier pour n'en regarder qu'une ; et il ne
|
|
6400
6824
|
* porte pas les cartes rangées. Une carte s'adresse par son identifiant.
|
|
6401
6825
|
*/
|
|
6402
|
-
getCard: protectedProcedure.input(
|
|
6826
|
+
getCard: protectedProcedure.input(z23.object({ boardId: z23.string().max(128), cardId: z23.string().max(128) })).query(async ({ input }) => {
|
|
6403
6827
|
const found = await kanbanStore.card(input.boardId, input.cardId);
|
|
6404
6828
|
if (!found) throw new Error(`Carte ${input.cardId} introuvable sur le tableau ${input.boardId}.`);
|
|
6405
6829
|
return { ...summarizeCard(found.card, input.boardId, ""), columnId: found.columnId };
|
|
6406
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
|
+
}),
|
|
6407
6854
|
/**
|
|
6408
6855
|
* Modifier une carte.
|
|
6409
6856
|
*
|
|
@@ -6413,16 +6860,16 @@ var kanbanRouter = router({
|
|
|
6413
6860
|
* Un champ absent n'est pas touché ; `null` l'efface. La distinction compte —
|
|
6414
6861
|
* changer un titre ne doit pas vider une couleur.
|
|
6415
6862
|
*/
|
|
6416
|
-
updateCard: protectedProcedure.input(
|
|
6417
|
-
boardId:
|
|
6418
|
-
cardId:
|
|
6419
|
-
title:
|
|
6420
|
-
content:
|
|
6421
|
-
priority:
|
|
6422
|
-
color:
|
|
6423
|
-
groups:
|
|
6424
|
-
deadline:
|
|
6425
|
-
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()
|
|
6426
6873
|
})).mutation(async ({ input }) => {
|
|
6427
6874
|
const { boardId, cardId, content, ...rest } = input;
|
|
6428
6875
|
const patch = { ...rest };
|
|
@@ -6433,10 +6880,10 @@ var kanbanRouter = router({
|
|
|
6433
6880
|
return summarizeCard(card, boardId, "");
|
|
6434
6881
|
}),
|
|
6435
6882
|
/** Ranger une carte, ou la reprendre. */
|
|
6436
|
-
setCardArchived: protectedProcedure.input(
|
|
6437
|
-
boardId:
|
|
6438
|
-
cardId:
|
|
6439
|
-
archived:
|
|
6883
|
+
setCardArchived: protectedProcedure.input(z23.object({
|
|
6884
|
+
boardId: z23.string().max(128),
|
|
6885
|
+
cardId: z23.string().max(128),
|
|
6886
|
+
archived: z23.boolean()
|
|
6440
6887
|
})).mutation(async ({ input }) => {
|
|
6441
6888
|
const ok = await kanbanStore.setCardArchived(input.boardId, input.cardId, input.archived);
|
|
6442
6889
|
if (!ok) throw new Error(`La carte ${input.cardId} n'a pas pu \xEAtre ${input.archived ? "archiv\xE9e" : "reprise"}.`);
|
|
@@ -6448,48 +6895,48 @@ var kanbanRouter = router({
|
|
|
6448
6895
|
* Sans cette liste, assigner revient à deviner un identifiant — et l'agent
|
|
6449
6896
|
* n'a aucun moyen de le trouver.
|
|
6450
6897
|
*/
|
|
6451
|
-
listMembers: protectedProcedure.input(
|
|
6898
|
+
listMembers: protectedProcedure.input(z23.object({ organizationId: z23.string().max(128).optional() })).query(async ({ input }) => ({ members: await kanbanStore.members(input.organizationId) })),
|
|
6452
6899
|
/**
|
|
6453
6900
|
* Fetch one card attachment from the cloud and drop it on this machine, so an
|
|
6454
6901
|
* agent can open it with its own file tools. The bytes never go through the
|
|
6455
6902
|
* prompt: the card context lists the attachments, this returns a path.
|
|
6456
6903
|
*/
|
|
6457
|
-
downloadAttachment: protectedProcedure.input(
|
|
6458
|
-
attachmentId:
|
|
6904
|
+
downloadAttachment: protectedProcedure.input(z23.object({
|
|
6905
|
+
attachmentId: z23.string().max(128),
|
|
6459
6906
|
// La pièce jointe appartient à une organisation : la nommer dit sous quel
|
|
6460
6907
|
// compte la demander.
|
|
6461
|
-
orgId:
|
|
6908
|
+
orgId: z23.string().max(128).optional(),
|
|
6462
6909
|
// À défaut, le tableau d'où vient la carte suffit : il désigne un compte
|
|
6463
6910
|
// comme le fait n'importe quel autre appel du kanban. Sans lui, la route
|
|
6464
6911
|
// retombait sur le compte unique du poste et refusait de servir dès qu'il
|
|
6465
6912
|
// y en avait plusieurs — alors que l'agent, qui n'a qu'un identifiant de
|
|
6466
6913
|
// fichier, n'avait aucun moyen de nommer l'organisation.
|
|
6467
|
-
boardId:
|
|
6914
|
+
boardId: z23.string().max(128).optional()
|
|
6468
6915
|
})).mutation(async ({ input }) => {
|
|
6469
6916
|
const id = input.attachmentId;
|
|
6470
6917
|
if (!ATTACHMENT_ID_REGEX.test(id)) {
|
|
6471
|
-
throw new
|
|
6918
|
+
throw new TRPCError19({ code: "BAD_REQUEST", message: "Invalid attachment id" });
|
|
6472
6919
|
}
|
|
6473
6920
|
const res = await cloudFetch2(`/api/kanban/file/${id}`, {
|
|
6474
6921
|
timeoutMs: 12e4,
|
|
6475
6922
|
accountId: await attachmentScope(input)
|
|
6476
6923
|
});
|
|
6477
6924
|
if (res.status === 404) {
|
|
6478
|
-
throw new
|
|
6925
|
+
throw new TRPCError19({ code: "NOT_FOUND", message: `Attachment ${id} not found` });
|
|
6479
6926
|
}
|
|
6480
6927
|
if (!res.ok) {
|
|
6481
|
-
throw new
|
|
6928
|
+
throw new TRPCError19({
|
|
6482
6929
|
code: res.status === 403 ? "FORBIDDEN" : "INTERNAL_SERVER_ERROR",
|
|
6483
6930
|
message: `Cloud refused the attachment (HTTP ${res.status})`
|
|
6484
6931
|
});
|
|
6485
6932
|
}
|
|
6486
6933
|
const declared = Number(res.headers.get("content-length") ?? 0);
|
|
6487
6934
|
if (declared > MAX_ATTACHMENT_BYTES) {
|
|
6488
|
-
throw new
|
|
6935
|
+
throw new TRPCError19({ code: "PAYLOAD_TOO_LARGE", message: "Attachment too large to download" });
|
|
6489
6936
|
}
|
|
6490
6937
|
const body = Buffer.from(await res.arrayBuffer());
|
|
6491
6938
|
if (body.length > MAX_ATTACHMENT_BYTES) {
|
|
6492
|
-
throw new
|
|
6939
|
+
throw new TRPCError19({ code: "PAYLOAD_TOO_LARGE", message: "Attachment too large to download" });
|
|
6493
6940
|
}
|
|
6494
6941
|
const contentType = res.headers.get("content-type") ?? "application/octet-stream";
|
|
6495
6942
|
const disposition = res.headers.get("content-disposition") ?? "";
|
|
@@ -6501,21 +6948,21 @@ var kanbanRouter = router({
|
|
|
6501
6948
|
});
|
|
6502
6949
|
|
|
6503
6950
|
// ../server/src/trpc/routers/terminal.ts
|
|
6504
|
-
import { z as
|
|
6505
|
-
import { TRPCError as
|
|
6951
|
+
import { z as z24 } from "zod";
|
|
6952
|
+
import { TRPCError as TRPCError20 } from "@trpc/server";
|
|
6506
6953
|
var terminalRouter = router({
|
|
6507
6954
|
/** Create a PTY shell session on the target agent */
|
|
6508
|
-
create: protectedProcedure.input(
|
|
6509
|
-
agentId:
|
|
6510
|
-
cwd:
|
|
6955
|
+
create: protectedProcedure.input(z24.object({
|
|
6956
|
+
agentId: z24.string().max(128),
|
|
6957
|
+
cwd: z24.string().max(512).optional(),
|
|
6511
6958
|
/** Anchors the terminal when no explicit cwd is given. */
|
|
6512
|
-
serviceId:
|
|
6513
|
-
projectId:
|
|
6959
|
+
serviceId: z24.string().max(128).optional(),
|
|
6960
|
+
projectId: z24.string().max(128).optional(),
|
|
6514
6961
|
/** L'organisation du projet : un id seul peut en nommer un par organisation. */
|
|
6515
|
-
orgId:
|
|
6516
|
-
cols:
|
|
6517
|
-
rows:
|
|
6518
|
-
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()
|
|
6519
6966
|
})).mutation(async ({ input }) => {
|
|
6520
6967
|
const result = await agentManager.agentMutation(
|
|
6521
6968
|
input.agentId,
|
|
@@ -6536,17 +6983,17 @@ var terminalRouter = router({
|
|
|
6536
6983
|
return result;
|
|
6537
6984
|
}),
|
|
6538
6985
|
/** Les sessions PTY encore vivantes sur l'agent (onglets à retrouver) */
|
|
6539
|
-
list: protectedProcedure.input(
|
|
6986
|
+
list: protectedProcedure.input(z24.object({ agentId: z24.string().max(128) })).query(async ({ input }) => {
|
|
6540
6987
|
return agentManager.agentQuery(
|
|
6541
6988
|
input.agentId,
|
|
6542
6989
|
"terminal.list"
|
|
6543
6990
|
);
|
|
6544
6991
|
}),
|
|
6545
6992
|
/** Renommer un onglet */
|
|
6546
|
-
rename: protectedProcedure.input(
|
|
6547
|
-
agentId:
|
|
6548
|
-
sessionId:
|
|
6549
|
-
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)
|
|
6550
6997
|
})).mutation(async ({ input }) => {
|
|
6551
6998
|
await agentManager.agentMutation(
|
|
6552
6999
|
input.agentId,
|
|
@@ -6556,10 +7003,10 @@ var terminalRouter = router({
|
|
|
6556
7003
|
return { ok: true };
|
|
6557
7004
|
}),
|
|
6558
7005
|
/** Send keystrokes / data to a PTY session */
|
|
6559
|
-
write: protectedProcedure.input(
|
|
6560
|
-
agentId:
|
|
6561
|
-
sessionId:
|
|
6562
|
-
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)
|
|
6563
7010
|
})).mutation(async ({ input }) => {
|
|
6564
7011
|
await agentManager.agentMutation(
|
|
6565
7012
|
input.agentId,
|
|
@@ -6569,11 +7016,11 @@ var terminalRouter = router({
|
|
|
6569
7016
|
return { ok: true };
|
|
6570
7017
|
}),
|
|
6571
7018
|
/** Resize the PTY */
|
|
6572
|
-
resize: protectedProcedure.input(
|
|
6573
|
-
agentId:
|
|
6574
|
-
sessionId:
|
|
6575
|
-
cols:
|
|
6576
|
-
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)
|
|
6577
7024
|
})).mutation(async ({ input }) => {
|
|
6578
7025
|
await agentManager.agentMutation(
|
|
6579
7026
|
input.agentId,
|
|
@@ -6583,9 +7030,9 @@ var terminalRouter = router({
|
|
|
6583
7030
|
return { ok: true };
|
|
6584
7031
|
}),
|
|
6585
7032
|
/** Destroy a PTY session */
|
|
6586
|
-
destroy: protectedProcedure.input(
|
|
6587
|
-
agentId:
|
|
6588
|
-
sessionId:
|
|
7033
|
+
destroy: protectedProcedure.input(z24.object({
|
|
7034
|
+
agentId: z24.string().max(128),
|
|
7035
|
+
sessionId: z24.string().max(128)
|
|
6589
7036
|
})).mutation(async ({ input }) => {
|
|
6590
7037
|
await agentManager.agentMutation(
|
|
6591
7038
|
input.agentId,
|
|
@@ -6595,14 +7042,14 @@ var terminalRouter = router({
|
|
|
6595
7042
|
return { ok: true };
|
|
6596
7043
|
}),
|
|
6597
7044
|
/** Stream PTY output — proxied via AgentWSBridge dynamic subscription */
|
|
6598
|
-
onData: protectedProcedure.input(
|
|
6599
|
-
agentId:
|
|
6600
|
-
sessionId:
|
|
7045
|
+
onData: protectedProcedure.input(z24.object({
|
|
7046
|
+
agentId: z24.string().max(128),
|
|
7047
|
+
sessionId: z24.string().max(128),
|
|
6601
7048
|
/** Rejouer l'historique de la session avant le flux temps réel. */
|
|
6602
|
-
replay:
|
|
7049
|
+
replay: z24.boolean().optional()
|
|
6603
7050
|
})).subscription(async function* ({ input }) {
|
|
6604
7051
|
const queue = [];
|
|
6605
|
-
let
|
|
7052
|
+
let resolve5 = null;
|
|
6606
7053
|
let done = false;
|
|
6607
7054
|
let unsubscribe;
|
|
6608
7055
|
try {
|
|
@@ -6612,21 +7059,21 @@ var terminalRouter = router({
|
|
|
6612
7059
|
{ sessionId: input.sessionId, replay: input.replay },
|
|
6613
7060
|
(data) => {
|
|
6614
7061
|
if (typeof data === "string") queue.push(data);
|
|
6615
|
-
if (
|
|
6616
|
-
|
|
6617
|
-
|
|
7062
|
+
if (resolve5) {
|
|
7063
|
+
resolve5();
|
|
7064
|
+
resolve5 = null;
|
|
6618
7065
|
}
|
|
6619
7066
|
},
|
|
6620
7067
|
() => {
|
|
6621
7068
|
done = true;
|
|
6622
|
-
if (
|
|
6623
|
-
|
|
6624
|
-
|
|
7069
|
+
if (resolve5) {
|
|
7070
|
+
resolve5();
|
|
7071
|
+
resolve5 = null;
|
|
6625
7072
|
}
|
|
6626
7073
|
}
|
|
6627
7074
|
);
|
|
6628
7075
|
} catch (err) {
|
|
6629
|
-
throw new
|
|
7076
|
+
throw new TRPCError20({
|
|
6630
7077
|
code: "BAD_REQUEST",
|
|
6631
7078
|
message: `Cannot subscribe to terminal: ${err.message}`
|
|
6632
7079
|
});
|
|
@@ -6637,7 +7084,7 @@ var terminalRouter = router({
|
|
|
6637
7084
|
yield queue.shift();
|
|
6638
7085
|
} else {
|
|
6639
7086
|
await new Promise((r) => {
|
|
6640
|
-
|
|
7087
|
+
resolve5 = r;
|
|
6641
7088
|
});
|
|
6642
7089
|
}
|
|
6643
7090
|
}
|
|
@@ -6648,27 +7095,27 @@ var terminalRouter = router({
|
|
|
6648
7095
|
});
|
|
6649
7096
|
|
|
6650
7097
|
// ../server/src/trpc/routers/utility.ts
|
|
6651
|
-
import { z as
|
|
7098
|
+
import { z as z25 } from "zod";
|
|
6652
7099
|
var MAX_WAIT_MS = 5 * 60 * 1e3;
|
|
6653
7100
|
var utilityRouter = router({
|
|
6654
|
-
wait: protectedProcedure.input(
|
|
6655
|
-
ms:
|
|
7101
|
+
wait: protectedProcedure.input(z25.object({
|
|
7102
|
+
ms: z25.number().int().min(0).max(MAX_WAIT_MS)
|
|
6656
7103
|
})).mutation(async ({ input }) => {
|
|
6657
|
-
await new Promise((
|
|
7104
|
+
await new Promise((resolve5) => setTimeout(resolve5, input.ms));
|
|
6658
7105
|
return { waited: input.ms };
|
|
6659
7106
|
})
|
|
6660
7107
|
});
|
|
6661
7108
|
|
|
6662
7109
|
// ../server/src/trpc/routers/wiki.ts
|
|
6663
|
-
import { z as
|
|
6664
|
-
import { TRPCError as
|
|
6665
|
-
import { readFile as
|
|
6666
|
-
import { resolve as resolvePath, join as
|
|
6667
|
-
var SCOPE_SCHEMA =
|
|
6668
|
-
|
|
6669
|
-
|
|
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")
|
|
6670
7117
|
]);
|
|
6671
|
-
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("/"), {
|
|
6672
7119
|
message: "Path must be a relative path without traversal"
|
|
6673
7120
|
});
|
|
6674
7121
|
var MAX_FILES = 500;
|
|
@@ -6688,36 +7135,36 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
6688
7135
|
"target",
|
|
6689
7136
|
"out"
|
|
6690
7137
|
]);
|
|
6691
|
-
var DocsEntrySchema =
|
|
6692
|
-
|
|
6693
|
-
|
|
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) })
|
|
6694
7141
|
]));
|
|
6695
7142
|
async function resolveScope(scope) {
|
|
6696
7143
|
if (scope === "global") {
|
|
6697
7144
|
const settings = await settingsManager.getSettings();
|
|
6698
7145
|
const sub = (settings.globalDocsPath?.trim() || "wiki").replace(/^[\\/]+|[\\/]+$/g, "");
|
|
6699
7146
|
if (sub.includes("..") || sub.includes("\0")) {
|
|
6700
|
-
throw new
|
|
7147
|
+
throw new TRPCError21({ code: "BAD_REQUEST", message: "Sous-chemin invalide" });
|
|
6701
7148
|
}
|
|
6702
7149
|
const dataDirAbs = resolvePath(env.DATA_DIR);
|
|
6703
|
-
await
|
|
6704
|
-
const base =
|
|
7150
|
+
await mkdir7(dataDirAbs, { recursive: true });
|
|
7151
|
+
const base = dirname5(await realpath2(dataDirAbs));
|
|
6705
7152
|
const target = resolvePath(base, sub);
|
|
6706
7153
|
if (target !== base && !(target + sep).startsWith(base + sep)) {
|
|
6707
|
-
throw new
|
|
7154
|
+
throw new TRPCError21({ code: "FORBIDDEN", message: "Hors du dossier de donn\xE9es du serveur" });
|
|
6708
7155
|
}
|
|
6709
|
-
await
|
|
6710
|
-
const root = await
|
|
7156
|
+
await mkdir7(target, { recursive: true });
|
|
7157
|
+
const root = await realpath2(target);
|
|
6711
7158
|
if (root !== base && !(root + sep).startsWith(base + sep)) {
|
|
6712
|
-
throw new
|
|
7159
|
+
throw new TRPCError21({ code: "FORBIDDEN", message: "Le dossier wiki r\xE9sout hors du dossier de donn\xE9es" });
|
|
6713
7160
|
}
|
|
6714
7161
|
return { mode: "local", root };
|
|
6715
7162
|
}
|
|
6716
7163
|
const serviceId = scope.slice("service:".length);
|
|
6717
7164
|
const service = await serviceStore.get(serviceId);
|
|
6718
|
-
if (!service) throw new
|
|
7165
|
+
if (!service) throw new TRPCError21({ code: "NOT_FOUND", message: "Service introuvable" });
|
|
6719
7166
|
const agentId = await resolveEffectiveAgentId(service);
|
|
6720
|
-
if (!agentId) throw new
|
|
7167
|
+
if (!agentId) throw new TRPCError21({ code: "BAD_REQUEST", message: "Aucun agent configur\xE9 pour ce service" });
|
|
6721
7168
|
const cwd = await resolveWorkspaceCwd({ serviceId, override: service.docsPath?.trim() || void 0 });
|
|
6722
7169
|
return { mode: "agent", agentId, cwd };
|
|
6723
7170
|
}
|
|
@@ -6733,7 +7180,7 @@ async function walkLocal(root, dir, depth, counter) {
|
|
|
6733
7180
|
for (const e of entries2) {
|
|
6734
7181
|
if (counter.n >= MAX_FILES) break;
|
|
6735
7182
|
if (e.name.startsWith(".") && e.name !== ".") continue;
|
|
6736
|
-
const abs =
|
|
7183
|
+
const abs = join9(dir, e.name);
|
|
6737
7184
|
const rel = relative2(root, abs).split(sep).join("/");
|
|
6738
7185
|
if (e.isDirectory()) {
|
|
6739
7186
|
if (IGNORED_DIRS.has(e.name)) continue;
|
|
@@ -6752,35 +7199,35 @@ async function walkLocal(root, dir, depth, counter) {
|
|
|
6752
7199
|
}
|
|
6753
7200
|
async function resolveSafeLocal(root, relPath, requireExist) {
|
|
6754
7201
|
if (relPath.includes("..") || relPath.startsWith("/")) {
|
|
6755
|
-
throw new
|
|
7202
|
+
throw new TRPCError21({ code: "BAD_REQUEST", message: "Chemin invalide" });
|
|
6756
7203
|
}
|
|
6757
7204
|
const target = resolvePath(root, relPath);
|
|
6758
7205
|
if (requireExist) {
|
|
6759
7206
|
let real;
|
|
6760
7207
|
try {
|
|
6761
|
-
real = await
|
|
7208
|
+
real = await realpath2(target);
|
|
6762
7209
|
} catch {
|
|
6763
|
-
throw new
|
|
7210
|
+
throw new TRPCError21({ code: "NOT_FOUND", message: "Chemin introuvable" });
|
|
6764
7211
|
}
|
|
6765
7212
|
if (real !== root && !(real + sep).startsWith(root + sep)) {
|
|
6766
|
-
throw new
|
|
7213
|
+
throw new TRPCError21({ code: "FORBIDDEN", message: "Hors du dossier autoris\xE9" });
|
|
6767
7214
|
}
|
|
6768
7215
|
return real;
|
|
6769
7216
|
}
|
|
6770
|
-
const parent =
|
|
7217
|
+
const parent = dirname5(target);
|
|
6771
7218
|
let realParent;
|
|
6772
7219
|
try {
|
|
6773
|
-
realParent = await
|
|
7220
|
+
realParent = await realpath2(parent);
|
|
6774
7221
|
} catch {
|
|
6775
7222
|
realParent = parent;
|
|
6776
7223
|
}
|
|
6777
7224
|
if (realParent !== root && !(realParent + sep).startsWith(root + sep)) {
|
|
6778
|
-
throw new
|
|
7225
|
+
throw new TRPCError21({ code: "FORBIDDEN", message: "Hors du dossier autoris\xE9" });
|
|
6779
7226
|
}
|
|
6780
7227
|
return resolvePath(realParent, target.slice(parent.length).replace(/^[\\/]+/, ""));
|
|
6781
7228
|
}
|
|
6782
7229
|
var wikiRouter = router({
|
|
6783
|
-
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 }) => {
|
|
6784
7231
|
const target = await resolveScope(input.scope);
|
|
6785
7232
|
if (target.mode === "agent") {
|
|
6786
7233
|
return agentManager.agentQuery(target.agentId, "wiki.listRepoMarkdown", { cwd: target.cwd });
|
|
@@ -6789,9 +7236,9 @@ var wikiRouter = router({
|
|
|
6789
7236
|
const entries2 = await walkLocal(target.root, target.root, 0, counter);
|
|
6790
7237
|
return { root: target.root, entries: entries2, truncated: counter.n >= MAX_FILES };
|
|
6791
7238
|
}),
|
|
6792
|
-
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 }) => {
|
|
6793
7240
|
if (!/\.(md|markdown)$/i.test(input.path)) {
|
|
6794
|
-
throw new
|
|
7241
|
+
throw new TRPCError21({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
|
|
6795
7242
|
}
|
|
6796
7243
|
const target = await resolveScope(input.scope);
|
|
6797
7244
|
if (target.mode === "agent") {
|
|
@@ -6799,27 +7246,27 @@ var wikiRouter = router({
|
|
|
6799
7246
|
}
|
|
6800
7247
|
const file = await resolveSafeLocal(target.root, input.path, true);
|
|
6801
7248
|
const s = await stat4(file);
|
|
6802
|
-
if (!s.isFile()) throw new
|
|
6803
|
-
if (s.size > MAX_FILE_BYTES) throw new
|
|
6804
|
-
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");
|
|
6805
7252
|
return { path: input.path, content };
|
|
6806
7253
|
}),
|
|
6807
|
-
write: protectedProcedure.input(
|
|
7254
|
+
write: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA, content: z26.string().max(5e6) })).mutation(async ({ input }) => {
|
|
6808
7255
|
if (!/\.(md|markdown)$/i.test(input.path)) {
|
|
6809
|
-
throw new
|
|
7256
|
+
throw new TRPCError21({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
|
|
6810
7257
|
}
|
|
6811
7258
|
const target = await resolveScope(input.scope);
|
|
6812
7259
|
if (target.mode === "agent") {
|
|
6813
7260
|
return agentManager.agentMutation(target.agentId, "wiki.writeMarkdown", { cwd: target.cwd, path: input.path, content: input.content });
|
|
6814
7261
|
}
|
|
6815
7262
|
const file = await resolveSafeLocal(target.root, input.path, false);
|
|
6816
|
-
await
|
|
7263
|
+
await mkdir7(dirname5(file), { recursive: true });
|
|
6817
7264
|
await writeFile5(file, input.content, "utf-8");
|
|
6818
7265
|
return { path: input.path, ok: true };
|
|
6819
7266
|
}),
|
|
6820
|
-
createFile: protectedProcedure.input(
|
|
7267
|
+
createFile: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
|
|
6821
7268
|
if (!/\.(md|markdown)$/i.test(input.path)) {
|
|
6822
|
-
throw new
|
|
7269
|
+
throw new TRPCError21({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
|
|
6823
7270
|
}
|
|
6824
7271
|
const target = await resolveScope(input.scope);
|
|
6825
7272
|
if (target.mode === "agent") {
|
|
@@ -6828,24 +7275,24 @@ var wikiRouter = router({
|
|
|
6828
7275
|
const file = await resolveSafeLocal(target.root, input.path, false);
|
|
6829
7276
|
try {
|
|
6830
7277
|
await stat4(file);
|
|
6831
|
-
throw new
|
|
7278
|
+
throw new TRPCError21({ code: "CONFLICT", message: "Fichier d\xE9j\xE0 existant" });
|
|
6832
7279
|
} catch (err) {
|
|
6833
|
-
if (err instanceof
|
|
7280
|
+
if (err instanceof TRPCError21) throw err;
|
|
6834
7281
|
}
|
|
6835
|
-
await
|
|
7282
|
+
await mkdir7(dirname5(file), { recursive: true });
|
|
6836
7283
|
await writeFile5(file, "", "utf-8");
|
|
6837
7284
|
return { path: input.path, ok: true };
|
|
6838
7285
|
}),
|
|
6839
|
-
createDir: protectedProcedure.input(
|
|
7286
|
+
createDir: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
|
|
6840
7287
|
const target = await resolveScope(input.scope);
|
|
6841
7288
|
if (target.mode === "agent") {
|
|
6842
7289
|
return agentManager.agentMutation(target.agentId, "wiki.createDir", { cwd: target.cwd, path: input.path });
|
|
6843
7290
|
}
|
|
6844
7291
|
const dir = await resolveSafeLocal(target.root, input.path, false);
|
|
6845
|
-
await
|
|
7292
|
+
await mkdir7(dir, { recursive: true });
|
|
6846
7293
|
return { path: input.path, ok: true };
|
|
6847
7294
|
}),
|
|
6848
|
-
rename: protectedProcedure.input(
|
|
7295
|
+
rename: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, fromPath: REL_PATH_SCHEMA, toPath: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
|
|
6849
7296
|
const target = await resolveScope(input.scope);
|
|
6850
7297
|
if (target.mode === "agent") {
|
|
6851
7298
|
return agentManager.agentMutation(target.agentId, "wiki.renamePath", { cwd: target.cwd, fromPath: input.fromPath, toPath: input.toPath });
|
|
@@ -6854,34 +7301,34 @@ var wikiRouter = router({
|
|
|
6854
7301
|
const to = await resolveSafeLocal(target.root, input.toPath, false);
|
|
6855
7302
|
try {
|
|
6856
7303
|
await stat4(to);
|
|
6857
|
-
throw new
|
|
7304
|
+
throw new TRPCError21({ code: "CONFLICT", message: "La cible existe d\xE9j\xE0" });
|
|
6858
7305
|
} catch (err) {
|
|
6859
|
-
if (err instanceof
|
|
7306
|
+
if (err instanceof TRPCError21) throw err;
|
|
6860
7307
|
}
|
|
6861
|
-
await
|
|
7308
|
+
await mkdir7(dirname5(to), { recursive: true });
|
|
6862
7309
|
await rename(from, to);
|
|
6863
7310
|
return { fromPath: input.fromPath, toPath: input.toPath, ok: true };
|
|
6864
7311
|
}),
|
|
6865
|
-
ensureRoot: protectedProcedure.input(
|
|
7312
|
+
ensureRoot: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA })).mutation(async ({ input }) => {
|
|
6866
7313
|
if (input.scope === "global") {
|
|
6867
7314
|
await resolveScope("global");
|
|
6868
7315
|
return { ok: true };
|
|
6869
7316
|
}
|
|
6870
7317
|
const serviceId = input.scope.slice("service:".length);
|
|
6871
7318
|
const service = await serviceStore.get(serviceId);
|
|
6872
|
-
if (!service) throw new
|
|
7319
|
+
if (!service) throw new TRPCError21({ code: "NOT_FOUND", message: "Service introuvable" });
|
|
6873
7320
|
const agentId = await resolveEffectiveAgentId(service);
|
|
6874
|
-
if (!agentId) throw new
|
|
7321
|
+
if (!agentId) throw new TRPCError21({ code: "BAD_REQUEST", message: "Aucun agent configur\xE9" });
|
|
6875
7322
|
const cwd = await resolveWorkspaceCwd({ serviceId, override: service.docsPath?.trim() || void 0 });
|
|
6876
7323
|
return agentManager.agentMutation(agentId, "wiki.ensureRoot", { cwd });
|
|
6877
7324
|
}),
|
|
6878
|
-
delete: protectedProcedure.input(
|
|
7325
|
+
delete: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
|
|
6879
7326
|
const target = await resolveScope(input.scope);
|
|
6880
7327
|
if (target.mode === "agent") {
|
|
6881
7328
|
return agentManager.agentMutation(target.agentId, "wiki.deletePath", { cwd: target.cwd, path: input.path });
|
|
6882
7329
|
}
|
|
6883
7330
|
const fp = await resolveSafeLocal(target.root, input.path, true);
|
|
6884
|
-
if (fp === target.root) throw new
|
|
7331
|
+
if (fp === target.root) throw new TRPCError21({ code: "FORBIDDEN", message: "Impossible de supprimer la racine" });
|
|
6885
7332
|
await rm2(fp, { recursive: true, force: true });
|
|
6886
7333
|
return { path: input.path, ok: true };
|
|
6887
7334
|
})
|
|
@@ -6903,14 +7350,14 @@ var configRouter = router({
|
|
|
6903
7350
|
});
|
|
6904
7351
|
|
|
6905
7352
|
// ../server/src/trpc/routers/cloud-sync.ts
|
|
6906
|
-
import { z as
|
|
6907
|
-
import { TRPCError as
|
|
7353
|
+
import { z as z27 } from "zod";
|
|
7354
|
+
import { TRPCError as TRPCError22 } from "@trpc/server";
|
|
6908
7355
|
|
|
6909
7356
|
// ../server/src/services/org-sync-scope.ts
|
|
6910
7357
|
import { access } from "fs/promises";
|
|
6911
|
-
import { join as
|
|
7358
|
+
import { join as join10 } from "path";
|
|
6912
7359
|
async function isMaterialized(root) {
|
|
6913
|
-
return access(
|
|
7360
|
+
return access(join10(root, "project.json")).then(() => true, () => false);
|
|
6914
7361
|
}
|
|
6915
7362
|
async function orgsToSync() {
|
|
6916
7363
|
const [stamps, stampedAccounts, accounts] = await Promise.all([
|
|
@@ -6985,23 +7432,23 @@ async function reconcileOrgCanary(orgId) {
|
|
|
6985
7432
|
}
|
|
6986
7433
|
|
|
6987
7434
|
// ../server/src/services/cloud-sync-bridge.ts
|
|
6988
|
-
import { randomUUID as
|
|
6989
|
-
import { mkdir as
|
|
7435
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
7436
|
+
import { mkdir as mkdir8, rm as rm3 } from "fs/promises";
|
|
6990
7437
|
import * as Y2 from "yjs";
|
|
6991
7438
|
|
|
6992
7439
|
// ../server/src/services/sync-doc-cache.ts
|
|
6993
7440
|
import { readFileSync as readFileSync2, renameSync, rmSync, writeFileSync, mkdirSync } from "fs";
|
|
6994
|
-
import { readFile as
|
|
6995
|
-
import { dirname as
|
|
6996
|
-
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";
|
|
6997
7444
|
import * as Y from "yjs";
|
|
6998
|
-
var
|
|
7445
|
+
var FILE2 = "sync-doc.bin";
|
|
6999
7446
|
function cachePath(orgId) {
|
|
7000
|
-
return
|
|
7447
|
+
return join11(orgVaultRoot(orgId), FILE2);
|
|
7001
7448
|
}
|
|
7002
7449
|
async function readSyncDoc(orgId) {
|
|
7003
7450
|
try {
|
|
7004
|
-
return new Uint8Array(await
|
|
7451
|
+
return new Uint8Array(await readFile6(cachePath(orgId)));
|
|
7005
7452
|
} catch {
|
|
7006
7453
|
return null;
|
|
7007
7454
|
}
|
|
@@ -7024,8 +7471,8 @@ function writeSyncDocSync(orgId, state) {
|
|
|
7024
7471
|
existing = new Uint8Array(readFileSync2(path));
|
|
7025
7472
|
} catch {
|
|
7026
7473
|
}
|
|
7027
|
-
mkdirSync(
|
|
7028
|
-
const tmp = `${path}.${process.pid}.${
|
|
7474
|
+
mkdirSync(dirname6(path), { recursive: true });
|
|
7475
|
+
const tmp = `${path}.${process.pid}.${randomUUID7()}.tmp`;
|
|
7029
7476
|
try {
|
|
7030
7477
|
writeFileSync(tmp, mergeWith(existing, state), { mode: 384 });
|
|
7031
7478
|
renameSync(tmp, path);
|
|
@@ -7078,7 +7525,7 @@ var OrgSyncConnection = class {
|
|
|
7078
7525
|
services = getCollection(this.doc, "services");
|
|
7079
7526
|
environments = getCollection(this.doc, "environments");
|
|
7080
7527
|
logParsers = getCollection(this.doc, PARSERS);
|
|
7081
|
-
conn =
|
|
7528
|
+
conn = randomUUID8();
|
|
7082
7529
|
link;
|
|
7083
7530
|
stopped = false;
|
|
7084
7531
|
initializing = true;
|
|
@@ -7128,8 +7575,8 @@ var OrgSyncConnection = class {
|
|
|
7128
7575
|
* Réglée quand le document tient l'état d'ouverture du cloud — ou quand il ne
|
|
7129
7576
|
* l'aura pas : session refusée, app trop ancienne, connexion arrêtée.
|
|
7130
7577
|
*/
|
|
7131
|
-
opened = new Promise((
|
|
7132
|
-
this.markOpened =
|
|
7578
|
+
opened = new Promise((resolve5) => {
|
|
7579
|
+
this.markOpened = resolve5;
|
|
7133
7580
|
});
|
|
7134
7581
|
/** Store applications started by remote events and not finished yet. */
|
|
7135
7582
|
applying = /* @__PURE__ */ new Set();
|
|
@@ -7511,7 +7958,7 @@ var OrgSyncConnection = class {
|
|
|
7511
7958
|
}
|
|
7512
7959
|
const isLocal = existing?.orgId === this.link.orgId;
|
|
7513
7960
|
if (!inDoc && !isLocal) return false;
|
|
7514
|
-
await
|
|
7961
|
+
await mkdir8(projectVaultRoot(this.link.orgId, projectId), { recursive: true });
|
|
7515
7962
|
this.tracked.add(projectId);
|
|
7516
7963
|
if (inDoc) await this.pullFromDoc([projectId]);
|
|
7517
7964
|
else await this.reconcileLocalToDoc();
|
|
@@ -7990,8 +8437,8 @@ var CloudSyncBridge = class {
|
|
|
7990
8437
|
let timer;
|
|
7991
8438
|
await Promise.race([
|
|
7992
8439
|
conn.opened,
|
|
7993
|
-
new Promise((
|
|
7994
|
-
timer = setTimeout(
|
|
8440
|
+
new Promise((resolve5) => {
|
|
8441
|
+
timer = setTimeout(resolve5, timeoutMs);
|
|
7995
8442
|
})
|
|
7996
8443
|
]);
|
|
7997
8444
|
clearTimeout(timer);
|
|
@@ -8008,9 +8455,9 @@ var CloudSyncBridge = class {
|
|
|
8008
8455
|
var cloudSyncBridge = new CloudSyncBridge();
|
|
8009
8456
|
|
|
8010
8457
|
// ../server/src/services/key-adoption.ts
|
|
8011
|
-
import { readdir as readdir3, readFile as
|
|
8012
|
-
import { dirname as
|
|
8013
|
-
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";
|
|
8014
8461
|
var ALGORITHM = "aes-256-gcm";
|
|
8015
8462
|
function isAgentTriplet(value) {
|
|
8016
8463
|
const hex = (v) => typeof v === "string" && /^[0-9a-f]+$/i.test(v);
|
|
@@ -8022,7 +8469,7 @@ function decryptTriplet(key, t) {
|
|
|
8022
8469
|
return decipher.update(t.encrypted, "hex", "utf-8") + decipher.final("utf-8");
|
|
8023
8470
|
}
|
|
8024
8471
|
function encryptTriplet(key, plaintext) {
|
|
8025
|
-
const iv =
|
|
8472
|
+
const iv = randomBytes3(16);
|
|
8026
8473
|
const cipher = createCipheriv2(ALGORITHM, key, iv);
|
|
8027
8474
|
const encrypted = cipher.update(plaintext, "utf-8", "hex") + cipher.final("hex");
|
|
8028
8475
|
return { encrypted, iv: iv.toString("hex"), authTag: cipher.getAuthTag().toString("hex") };
|
|
@@ -8076,7 +8523,7 @@ async function jsonNamesIn(dir) {
|
|
|
8076
8523
|
async function jsonFilesIn(root) {
|
|
8077
8524
|
const names = await jsonNamesIn(root);
|
|
8078
8525
|
for (const sub of ENCRYPTED_SUBDIRS) {
|
|
8079
|
-
names.push(...(await jsonNamesIn(
|
|
8526
|
+
names.push(...(await jsonNamesIn(join12(root, sub))).map((n) => join12(sub, n)));
|
|
8080
8527
|
}
|
|
8081
8528
|
return names;
|
|
8082
8529
|
}
|
|
@@ -8114,10 +8561,10 @@ async function adoptOrgKey(orgId, newKey, masterPassword, now) {
|
|
|
8114
8561
|
const touchedRoots = /* @__PURE__ */ new Set();
|
|
8115
8562
|
for (const vaultRoot of await rootsOfOrg(orgId)) {
|
|
8116
8563
|
for (const name of await jsonFilesIn(vaultRoot)) {
|
|
8117
|
-
const path =
|
|
8564
|
+
const path = join12(vaultRoot, name);
|
|
8118
8565
|
let parsed;
|
|
8119
8566
|
try {
|
|
8120
|
-
parsed = JSON.parse(await
|
|
8567
|
+
parsed = JSON.parse(await readFile7(path, "utf-8"));
|
|
8121
8568
|
} catch {
|
|
8122
8569
|
continue;
|
|
8123
8570
|
}
|
|
@@ -8131,15 +8578,15 @@ async function adoptOrgKey(orgId, newKey, masterPassword, now) {
|
|
|
8131
8578
|
const stamp = now.toISOString().replace(/[:.]/g, "-").substring(0, 19);
|
|
8132
8579
|
const backups = [];
|
|
8133
8580
|
for (const vaultRoot of touchedRoots) {
|
|
8134
|
-
const backupDir =
|
|
8135
|
-
await
|
|
8581
|
+
const backupDir = join12(vaultRoot, `backup-key-${stamp}`);
|
|
8582
|
+
await mkdir9(backupDir, { recursive: true });
|
|
8136
8583
|
for (const name of await jsonFilesIn(vaultRoot)) {
|
|
8137
|
-
await
|
|
8138
|
-
await copyFile(
|
|
8584
|
+
await mkdir9(dirname7(join12(backupDir, name)), { recursive: true });
|
|
8585
|
+
await copyFile(join12(vaultRoot, name), join12(backupDir, name));
|
|
8139
8586
|
}
|
|
8140
8587
|
for (const name of [".encryption.key.enc", ".runeya-verify"]) {
|
|
8141
|
-
if (await exists(
|
|
8142
|
-
await copyFile(
|
|
8588
|
+
if (await exists(join12(vaultRoot, name))) {
|
|
8589
|
+
await copyFile(join12(vaultRoot, name), join12(backupDir, name));
|
|
8143
8590
|
}
|
|
8144
8591
|
}
|
|
8145
8592
|
backups.push(backupDir);
|
|
@@ -8158,7 +8605,7 @@ async function adoptOrgKey(orgId, newKey, masterPassword, now) {
|
|
|
8158
8605
|
return { converted: pending.map(({ path, values }) => ({ file: path, values })), backups };
|
|
8159
8606
|
}
|
|
8160
8607
|
async function resetOrgKeyToFresh(orgId, masterPassword, now) {
|
|
8161
|
-
const freshKey =
|
|
8608
|
+
const freshKey = randomBytes3(32);
|
|
8162
8609
|
if (isVaultLocked(orgId) || !getEncryptionKey(orgId)) {
|
|
8163
8610
|
await readEncryptedKey(await keyRoot(), masterPassword);
|
|
8164
8611
|
await installOrgKey(orgId, freshKey, masterPassword);
|
|
@@ -8194,7 +8641,7 @@ async function assertOrgReadable(orgId) {
|
|
|
8194
8641
|
await assertOrgVaultReadable(orgId);
|
|
8195
8642
|
} catch (err) {
|
|
8196
8643
|
if (err instanceof OrgVaultUnreadableError) {
|
|
8197
|
-
throw new
|
|
8644
|
+
throw new TRPCError22({ code: "FORBIDDEN", message: err.message });
|
|
8198
8645
|
}
|
|
8199
8646
|
throw err;
|
|
8200
8647
|
}
|
|
@@ -8204,10 +8651,10 @@ async function assertOrgKey(orgId) {
|
|
|
8204
8651
|
await reconcileOrgCanary(orgId);
|
|
8205
8652
|
} catch (err) {
|
|
8206
8653
|
if (err instanceof OrgKeyMismatchError) {
|
|
8207
|
-
throw new
|
|
8654
|
+
throw new TRPCError22({ code: "FORBIDDEN", message: err.message });
|
|
8208
8655
|
}
|
|
8209
8656
|
if (err instanceof CanaryUnavailableError) {
|
|
8210
|
-
throw new
|
|
8657
|
+
throw new TRPCError22({ code: "PRECONDITION_FAILED", message: err.message });
|
|
8211
8658
|
}
|
|
8212
8659
|
throw err;
|
|
8213
8660
|
}
|
|
@@ -8224,14 +8671,14 @@ var cloudSyncRouter = router({
|
|
|
8224
8671
|
* "open this project" is the whole gesture.
|
|
8225
8672
|
*/
|
|
8226
8673
|
trackProject: protectedProcedure.input(
|
|
8227
|
-
|
|
8228
|
-
orgId:
|
|
8229
|
-
cloudUrl:
|
|
8230
|
-
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),
|
|
8231
8678
|
// Le compte sous lequel cette organisation a été listée. Tamponné avec
|
|
8232
8679
|
// elle : c'est ce qui permettra à tout appel ultérieur de retrouver le
|
|
8233
8680
|
// bon token sans qu'on ait à le lui redire.
|
|
8234
|
-
accountId:
|
|
8681
|
+
accountId: z27.string().max(64).optional()
|
|
8235
8682
|
})
|
|
8236
8683
|
).mutation(async ({ input }) => {
|
|
8237
8684
|
await assertOrgReadable(input.orgId);
|
|
@@ -8253,7 +8700,7 @@ var cloudSyncRouter = router({
|
|
|
8253
8700
|
return { pulled, state: syncState(input.orgId) };
|
|
8254
8701
|
} catch (err) {
|
|
8255
8702
|
if (err instanceof ProjectHeldElsewhereError) {
|
|
8256
|
-
throw new
|
|
8703
|
+
throw new TRPCError22({ code: "CONFLICT", message: err.code, cause: err });
|
|
8257
8704
|
}
|
|
8258
8705
|
throw err;
|
|
8259
8706
|
}
|
|
@@ -8272,22 +8719,22 @@ var cloudSyncRouter = router({
|
|
|
8272
8719
|
* fusionné.
|
|
8273
8720
|
*/
|
|
8274
8721
|
publishProject: protectedProcedure.input(
|
|
8275
|
-
|
|
8276
|
-
orgId:
|
|
8277
|
-
cloudUrl:
|
|
8278
|
-
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),
|
|
8279
8726
|
// Le compte sous lequel cette organisation a été listée. Tamponné avec
|
|
8280
8727
|
// elle : c'est ce qui permettra à tout appel ultérieur de retrouver le
|
|
8281
8728
|
// bon token sans qu'on ait à le lui redire.
|
|
8282
|
-
accountId:
|
|
8729
|
+
accountId: z27.string().max(64).optional()
|
|
8283
8730
|
})
|
|
8284
8731
|
).mutation(async ({ input }) => {
|
|
8285
8732
|
const local = await projectStore.get({ id: input.projectId, orgId: null });
|
|
8286
8733
|
if (!local) {
|
|
8287
|
-
throw new
|
|
8734
|
+
throw new TRPCError22({ code: "NOT_FOUND", message: "PROJECT_NOT_LOCAL" });
|
|
8288
8735
|
}
|
|
8289
8736
|
if (await projectStore.get({ id: input.projectId, orgId: input.orgId })) {
|
|
8290
|
-
throw new
|
|
8737
|
+
throw new TRPCError22({ code: "CONFLICT", message: "PROJECT_ALREADY_IN_ORG" });
|
|
8291
8738
|
}
|
|
8292
8739
|
await assertOrgReadable(input.orgId);
|
|
8293
8740
|
const connected = cloudSyncBridge.states().some((s) => s.orgId === input.orgId);
|
|
@@ -8312,7 +8759,7 @@ var cloudSyncRouter = router({
|
|
|
8312
8759
|
await cloudSyncBridge.followProject(input.orgId, input.projectId);
|
|
8313
8760
|
} catch (err) {
|
|
8314
8761
|
if (err instanceof ProjectHeldElsewhereError) {
|
|
8315
|
-
throw new
|
|
8762
|
+
throw new TRPCError22({ code: "CONFLICT", message: err.code, cause: err });
|
|
8316
8763
|
}
|
|
8317
8764
|
throw err;
|
|
8318
8765
|
}
|
|
@@ -8326,16 +8773,16 @@ var cloudSyncRouter = router({
|
|
|
8326
8773
|
* publié. Le cloud, lui, ne peut pas répondre — il n'a que du chiffré sous
|
|
8327
8774
|
* les yeux — donc le canari est rapatrié et jugé ici.
|
|
8328
8775
|
*/
|
|
8329
|
-
checkOrgKey: protectedProcedure.input(
|
|
8330
|
-
orgId:
|
|
8331
|
-
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)
|
|
8332
8779
|
})).query(async ({ input }) => {
|
|
8333
8780
|
const res = await cloudFetch2(
|
|
8334
8781
|
`/api/auth/organization/canary?organizationId=${encodeURIComponent(input.orgId)}`,
|
|
8335
8782
|
{ method: "GET", accountId: await accountForOrgScope(input.orgId) }
|
|
8336
8783
|
);
|
|
8337
8784
|
if (!res.ok) {
|
|
8338
|
-
throw new
|
|
8785
|
+
throw new TRPCError22({ code: "BAD_GATEWAY", message: await cloudMessage(res) || `CANARY_READ_FAILED (${res.status})` });
|
|
8339
8786
|
}
|
|
8340
8787
|
const { canary } = await res.json();
|
|
8341
8788
|
if (typeof canary !== "string") return { matches: false, published: false };
|
|
@@ -8350,7 +8797,7 @@ var cloudSyncRouter = router({
|
|
|
8350
8797
|
* que l'interface puisse proposer ni l'adoption ni la remise à zéro — le seul
|
|
8351
8798
|
* chemin qui la répare. Rien n'est écrit ici, ni sur le disque ni sur le cloud.
|
|
8352
8799
|
*/
|
|
8353
|
-
orgKeyStatus: protectedProcedure.input(
|
|
8800
|
+
orgKeyStatus: protectedProcedure.input(z27.object({ orgId: z27.string().min(1).max(128) })).query(async ({ input }) => {
|
|
8354
8801
|
const key = getEncryptionKey(input.orgId);
|
|
8355
8802
|
if (!key) return { status: "unknown" };
|
|
8356
8803
|
const res = await cloudFetch2(
|
|
@@ -8373,10 +8820,10 @@ var cloudSyncRouter = router({
|
|
|
8373
8820
|
* Bornée au coffre de l'organisation et à ceux de ses projets : les autres
|
|
8374
8821
|
* organisations et la machine gardent leur clé.
|
|
8375
8822
|
*/
|
|
8376
|
-
adoptOrgKey: protectedProcedure.input(
|
|
8377
|
-
orgId:
|
|
8378
|
-
keyHex:
|
|
8379
|
-
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)
|
|
8380
8827
|
})).mutation(async ({ input }) => {
|
|
8381
8828
|
const report = await adoptOrgKey(
|
|
8382
8829
|
input.orgId,
|
|
@@ -8406,14 +8853,14 @@ var cloudSyncRouter = router({
|
|
|
8406
8853
|
* Ce qui a déjà été synchronisé sous l'ancienne clé de référence reste
|
|
8407
8854
|
* illisible : aucune clé d'ici ne l'ouvre. L'interface le dit avant.
|
|
8408
8855
|
*/
|
|
8409
|
-
resetOrgKey: protectedProcedure.input(
|
|
8410
|
-
orgId:
|
|
8411
|
-
masterPassword:
|
|
8856
|
+
resetOrgKey: protectedProcedure.input(z27.object({
|
|
8857
|
+
orgId: z27.string().min(1).max(128),
|
|
8858
|
+
masterPassword: z27.string().min(1).max(1024)
|
|
8412
8859
|
})).mutation(async ({ input }) => {
|
|
8413
8860
|
const { keyHex } = await resetOrgKeyToFresh(input.orgId, input.masterPassword, /* @__PURE__ */ new Date());
|
|
8414
8861
|
const canary = await readVerifyFile(orgVaultRoot(input.orgId));
|
|
8415
8862
|
if (!canary) {
|
|
8416
|
-
throw new
|
|
8863
|
+
throw new TRPCError22({
|
|
8417
8864
|
code: "PRECONDITION_FAILED",
|
|
8418
8865
|
message: "Le coffre de cette organisation n'a pas re\xE7u sa cl\xE9."
|
|
8419
8866
|
});
|
|
@@ -8425,7 +8872,7 @@ var cloudSyncRouter = router({
|
|
|
8425
8872
|
});
|
|
8426
8873
|
if (!res.ok) {
|
|
8427
8874
|
const detail = await cloudMessage(res);
|
|
8428
|
-
throw new
|
|
8875
|
+
throw new TRPCError22({
|
|
8429
8876
|
code: res.status === 403 ? "FORBIDDEN" : "BAD_GATEWAY",
|
|
8430
8877
|
message: detail || `ORG_KEY_RESET_FAILED (${res.status})`
|
|
8431
8878
|
});
|
|
@@ -8441,9 +8888,9 @@ var cloudSyncRouter = router({
|
|
|
8441
8888
|
* every other member and for this one on another machine.
|
|
8442
8889
|
*/
|
|
8443
8890
|
untrackProject: protectedProcedure.input(
|
|
8444
|
-
|
|
8445
|
-
orgId:
|
|
8446
|
-
projectId:
|
|
8891
|
+
z27.object({
|
|
8892
|
+
orgId: z27.string().min(1).max(128),
|
|
8893
|
+
projectId: z27.string().min(1).max(128)
|
|
8447
8894
|
})
|
|
8448
8895
|
).mutation(async ({ input }) => {
|
|
8449
8896
|
await cloudSyncBridge.unfollowProject(input.orgId, input.projectId);
|
|
@@ -8459,9 +8906,9 @@ var cloudSyncRouter = router({
|
|
|
8459
8906
|
* membre, ce qui évite d'effacer les fichiers si le cloud a refusé l'ordre.
|
|
8460
8907
|
*/
|
|
8461
8908
|
deleteProjectFromOrg: protectedProcedure.input(
|
|
8462
|
-
|
|
8463
|
-
orgId:
|
|
8464
|
-
projectId:
|
|
8909
|
+
z27.object({
|
|
8910
|
+
orgId: z27.string().min(1).max(128),
|
|
8911
|
+
projectId: z27.string().min(1).max(128)
|
|
8465
8912
|
})
|
|
8466
8913
|
).mutation(async ({ input }) => {
|
|
8467
8914
|
let res;
|
|
@@ -8474,8 +8921,8 @@ var cloudSyncRouter = router({
|
|
|
8474
8921
|
accountId: await accountForOrgScope(input.orgId)
|
|
8475
8922
|
});
|
|
8476
8923
|
} catch (err) {
|
|
8477
|
-
if (err instanceof
|
|
8478
|
-
throw new
|
|
8924
|
+
if (err instanceof TRPCError22 && err.code === "PRECONDITION_FAILED") {
|
|
8925
|
+
throw new TRPCError22({ code: "PRECONDITION_FAILED", message: "CLOUD_NOT_LINKED" });
|
|
8479
8926
|
}
|
|
8480
8927
|
throw err;
|
|
8481
8928
|
}
|
|
@@ -8483,12 +8930,12 @@ var cloudSyncRouter = router({
|
|
|
8483
8930
|
const detail = await cloudMessage(res);
|
|
8484
8931
|
if (res.status === 403) {
|
|
8485
8932
|
const code = /not a member/i.test(detail) ? "PROJECT_NOT_MEMBER" : "PROJECT_DELETE_FORBIDDEN";
|
|
8486
|
-
throw new
|
|
8933
|
+
throw new TRPCError22({ code: "FORBIDDEN", message: code, cause: new Error(detail) });
|
|
8487
8934
|
}
|
|
8488
8935
|
if (res.status === 404) {
|
|
8489
|
-
throw new
|
|
8936
|
+
throw new TRPCError22({ code: "NOT_FOUND", message: "PROJECT_NOT_IN_ORG", cause: new Error(detail) });
|
|
8490
8937
|
}
|
|
8491
|
-
throw new
|
|
8938
|
+
throw new TRPCError22({
|
|
8492
8939
|
code: "BAD_GATEWAY",
|
|
8493
8940
|
message: detail || `PROJECT_DELETE_FAILED (${res.status})`
|
|
8494
8941
|
});
|
|
@@ -8517,10 +8964,10 @@ var cloudSyncRouter = router({
|
|
|
8517
8964
|
* d'un autre. Même forme que `rememberOrgNames`, et pour la même raison :
|
|
8518
8965
|
* ce que seul le cloud sait, on le recopie quand il le dit.
|
|
8519
8966
|
*/
|
|
8520
|
-
rememberOrgAccounts: protectedProcedure.input(
|
|
8521
|
-
orgs:
|
|
8522
|
-
orgId:
|
|
8523
|
-
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)
|
|
8524
8971
|
})).max(200)
|
|
8525
8972
|
})).mutation(async ({ input }) => {
|
|
8526
8973
|
const candidates = /* @__PURE__ */ new Map();
|
|
@@ -8547,10 +8994,10 @@ var cloudSyncRouter = router({
|
|
|
8547
8994
|
}
|
|
8548
8995
|
return { remembered };
|
|
8549
8996
|
}),
|
|
8550
|
-
rememberOrgNames: protectedProcedure.input(
|
|
8551
|
-
orgs:
|
|
8552
|
-
orgId:
|
|
8553
|
-
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)
|
|
8554
9001
|
})).max(200)
|
|
8555
9002
|
})).mutation(async ({ input }) => {
|
|
8556
9003
|
for (const { orgId, name } of input.orgs) await rememberOrgName(orgId, name);
|
|
@@ -8580,7 +9027,7 @@ var cloudSyncRouter = router({
|
|
|
8580
9027
|
* Un 404 efface le logo retenu — l'organisation en a retiré un — tandis qu'un
|
|
8581
9028
|
* réseau muet laisse en place ce qui est déjà là : hors ligne, on garde.
|
|
8582
9029
|
*/
|
|
8583
|
-
cacheOrgLogos: protectedProcedure.input(
|
|
9030
|
+
cacheOrgLogos: protectedProcedure.input(z27.object({ orgIds: z27.array(z27.string().min(1).max(128)).max(200) })).mutation(async ({ input }) => {
|
|
8584
9031
|
let cached = 0;
|
|
8585
9032
|
for (const orgId of input.orgIds) {
|
|
8586
9033
|
const base = await orgCloudUrl(orgId);
|
|
@@ -8637,22 +9084,22 @@ var cloudSyncRouter = router({
|
|
|
8637
9084
|
|
|
8638
9085
|
// ../server/src/trpc/routers/filesystem.ts
|
|
8639
9086
|
import { readdir as readdir4, stat as stat6 } from "fs/promises";
|
|
8640
|
-
import { homedir as
|
|
8641
|
-
import { isAbsolute as isAbsolute3, join as
|
|
8642
|
-
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";
|
|
8643
9090
|
var MAX_ENTRIES2 = 500;
|
|
8644
|
-
var PATH_SCHEMA =
|
|
9091
|
+
var PATH_SCHEMA = z28.string().min(1).max(4096);
|
|
8645
9092
|
function safeResolve(input) {
|
|
8646
9093
|
if (input.includes("\0")) throw new Error("Chemin invalide");
|
|
8647
|
-
const expanded = input.startsWith("~") ?
|
|
9094
|
+
const expanded = input.startsWith("~") ? join13(homedir4(), input.slice(1)) : input;
|
|
8648
9095
|
if (!isAbsolute3(expanded)) throw new Error("Le chemin doit \xEAtre absolu");
|
|
8649
|
-
return
|
|
9096
|
+
return resolve3(expanded);
|
|
8650
9097
|
}
|
|
8651
9098
|
var filesystemRouter = router({
|
|
8652
9099
|
/** Where a picker should open when the user has chosen nothing yet. */
|
|
8653
9100
|
suggestedRoots: protectedProcedure.query(async () => {
|
|
8654
9101
|
const roots = [
|
|
8655
|
-
{ label: "home", path:
|
|
9102
|
+
{ label: "home", path: homedir4() },
|
|
8656
9103
|
// The launch directory, taken from the vault rather than the process.
|
|
8657
9104
|
{ label: "cwd", path: await resolveWorkspaceCwd({}) }
|
|
8658
9105
|
];
|
|
@@ -8665,7 +9112,7 @@ var filesystemRouter = router({
|
|
|
8665
9112
|
* Hidden entries are skipped: they are noise for choosing a workspace, and
|
|
8666
9113
|
* `.runeya` itself would invite placing a project inside another's data.
|
|
8667
9114
|
*/
|
|
8668
|
-
listDirectories: protectedProcedure.input(
|
|
9115
|
+
listDirectories: protectedProcedure.input(z28.object({ path: PATH_SCHEMA })).query(async ({ input }) => {
|
|
8669
9116
|
const dir = safeResolve(input.path);
|
|
8670
9117
|
const info = await stat6(dir).catch(() => null);
|
|
8671
9118
|
if (!info?.isDirectory()) {
|
|
@@ -8680,13 +9127,13 @@ var filesystemRouter = router({
|
|
|
8680
9127
|
const truncated = names.length > MAX_ENTRIES2;
|
|
8681
9128
|
const entries2 = [];
|
|
8682
9129
|
for (const name of names.slice(0, MAX_ENTRIES2)) {
|
|
8683
|
-
const path =
|
|
9130
|
+
const path = join13(dir, name);
|
|
8684
9131
|
entries2.push({ name, path, hasRuneyaProject: await holdsRuneyaProject(path) });
|
|
8685
9132
|
}
|
|
8686
9133
|
return {
|
|
8687
9134
|
path: dir,
|
|
8688
9135
|
// Null at the filesystem root, which is what stops "up" from looping.
|
|
8689
|
-
parent:
|
|
9136
|
+
parent: dirname8(dir) === dir ? null : dirname8(dir),
|
|
8690
9137
|
name: basename(dir) || dir,
|
|
8691
9138
|
entries: entries2,
|
|
8692
9139
|
truncated,
|
|
@@ -8698,7 +9145,7 @@ var filesystemRouter = router({
|
|
|
8698
9145
|
* qui s'y trouve, ou rien (déjà dans la liste, lié à un projet d'organisation,
|
|
8699
9146
|
* racine machine).
|
|
8700
9147
|
*/
|
|
8701
|
-
checkLocalProjectTarget: protectedProcedure.input(
|
|
9148
|
+
checkLocalProjectTarget: protectedProcedure.input(z28.object({ path: PATH_SCHEMA })).query(async ({ input }) => {
|
|
8702
9149
|
const dir = safeResolve(input.path);
|
|
8703
9150
|
const info = await stat6(dir).catch(() => null);
|
|
8704
9151
|
if (!info?.isDirectory()) return { path: dir, status: "missing", projectName: null };
|
|
@@ -8710,10 +9157,10 @@ var filesystemRouter = router({
|
|
|
8710
9157
|
* Two projects in one directory is what makes services run against the wrong
|
|
8711
9158
|
* code, so a directory that already holds one is refused outright.
|
|
8712
9159
|
*/
|
|
8713
|
-
checkProjectTarget: protectedProcedure.input(
|
|
9160
|
+
checkProjectTarget: protectedProcedure.input(z28.object({
|
|
8714
9161
|
path: PATH_SCHEMA,
|
|
8715
|
-
projectId:
|
|
8716
|
-
orgId:
|
|
9162
|
+
projectId: z28.string().min(1).max(128),
|
|
9163
|
+
orgId: z28.string().max(128).nullable().optional()
|
|
8717
9164
|
})).query(async ({ input }) => {
|
|
8718
9165
|
const dir = safeResolve(input.path);
|
|
8719
9166
|
const info = await stat6(dir).catch(() => null);
|
|
@@ -8732,15 +9179,15 @@ var filesystemRouter = router({
|
|
|
8732
9179
|
});
|
|
8733
9180
|
|
|
8734
9181
|
// ../server/src/trpc/routers/desktop.ts
|
|
8735
|
-
import { z as
|
|
9182
|
+
import { z as z29 } from "zod";
|
|
8736
9183
|
import { isAbsolute as isAbsolute5 } from "path";
|
|
8737
|
-
import { TRPCError as
|
|
9184
|
+
import { TRPCError as TRPCError23 } from "@trpc/server";
|
|
8738
9185
|
|
|
8739
9186
|
// ../server/src/services/desktop-launcher.ts
|
|
8740
|
-
import { execFile as
|
|
9187
|
+
import { execFile as execFile5 } from "child_process";
|
|
8741
9188
|
import { isAbsolute as isAbsolute4 } from "path";
|
|
8742
9189
|
import { promisify as promisify3 } from "util";
|
|
8743
|
-
var exec = promisify3(
|
|
9190
|
+
var exec = promisify3(execFile5);
|
|
8744
9191
|
var INSTALL_TIMEOUT_MS2 = 3e4;
|
|
8745
9192
|
var cli = null;
|
|
8746
9193
|
function registerCli(location) {
|
|
@@ -8818,45 +9265,45 @@ var desktopRouter = router({
|
|
|
8818
9265
|
* dessinée par le serveur. Sans cette annonce elle garderait la teinte figée
|
|
8819
9266
|
* du fichier livré avec le CLI. Rien n'est persisté — voir `tray-accent.ts`.
|
|
8820
9267
|
*/
|
|
8821
|
-
setTrayAccent: protectedProcedure.input(
|
|
9268
|
+
setTrayAccent: protectedProcedure.input(z29.object({
|
|
8822
9269
|
// `#rgb` ou `#rrggbb` : la forme est vérifiée ici, pas rabotée plus loin.
|
|
8823
|
-
color:
|
|
9270
|
+
color: z29.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/).nullable()
|
|
8824
9271
|
})).mutation(({ input }) => {
|
|
8825
9272
|
setTrayAccent(input.color);
|
|
8826
9273
|
return { ok: true };
|
|
8827
9274
|
}),
|
|
8828
|
-
installLauncher: protectedProcedure.input(
|
|
8829
|
-
name:
|
|
9275
|
+
installLauncher: protectedProcedure.input(z29.object({
|
|
9276
|
+
name: z29.string().trim().min(1).max(120),
|
|
8830
9277
|
/**
|
|
8831
9278
|
* `null` : raccourci global, sans dossier épinglé. Sinon un chemin
|
|
8832
9279
|
* absolu, refusé autrement : le dossier devient un argument positionnel
|
|
8833
9280
|
* du CLI, où une valeur commençant par un tiret passerait pour un drapeau.
|
|
8834
9281
|
*/
|
|
8835
|
-
workingDir:
|
|
9282
|
+
workingDir: z29.string().max(4096).refine(isAbsolute5, {
|
|
8836
9283
|
message: "Le dossier de travail doit \xEAtre un chemin absolu."
|
|
8837
9284
|
}).nullable(),
|
|
8838
|
-
port:
|
|
8839
|
-
agentPort:
|
|
9285
|
+
port: z29.number().int().min(1).max(65535),
|
|
9286
|
+
agentPort: z29.number().int().min(1).max(65535),
|
|
8840
9287
|
/** Projet ouvert directement par le raccourci (`--project`). Absent : liste des projets. */
|
|
8841
|
-
projectId:
|
|
9288
|
+
projectId: z29.string().max(128).regex(LAUNCHER_PROJECT_ID_PATTERN).nullable().optional()
|
|
8842
9289
|
})).mutation(async ({ input }) => {
|
|
8843
9290
|
try {
|
|
8844
9291
|
return await installLauncher(input);
|
|
8845
9292
|
} catch (err) {
|
|
8846
9293
|
const message = err.message;
|
|
8847
9294
|
if (message === "launcher-working-dir-not-absolute") {
|
|
8848
|
-
throw new
|
|
9295
|
+
throw new TRPCError23({
|
|
8849
9296
|
code: "BAD_REQUEST",
|
|
8850
9297
|
message: "Le dossier de travail doit \xEAtre un chemin absolu."
|
|
8851
9298
|
});
|
|
8852
9299
|
}
|
|
8853
9300
|
if (message === "launcher-unavailable") {
|
|
8854
|
-
throw new
|
|
9301
|
+
throw new TRPCError23({
|
|
8855
9302
|
code: "PRECONDITION_FAILED",
|
|
8856
9303
|
message: "Cette Runeya ne sait pas o\xF9 trouver son CLI : cr\xE9ez le raccourci avec `runeya install-desktop`."
|
|
8857
9304
|
});
|
|
8858
9305
|
}
|
|
8859
|
-
throw new
|
|
9306
|
+
throw new TRPCError23({ code: "INTERNAL_SERVER_ERROR", message });
|
|
8860
9307
|
}
|
|
8861
9308
|
})
|
|
8862
9309
|
});
|
|
@@ -8868,6 +9315,8 @@ var appRouter = router({
|
|
|
8868
9315
|
appUpdate: appUpdateRouter,
|
|
8869
9316
|
changelog: changelogRouter,
|
|
8870
9317
|
diagnostics: diagnosticsRouter,
|
|
9318
|
+
mcpPairing: mcpPairingRouter,
|
|
9319
|
+
aiClients: aiClientsRouter,
|
|
8871
9320
|
project: projectRouter,
|
|
8872
9321
|
service: serviceRouter,
|
|
8873
9322
|
agent: agentRouter,
|
|
@@ -8956,8 +9405,8 @@ async function ensureFilePermissions() {
|
|
|
8956
9405
|
}
|
|
8957
9406
|
|
|
8958
9407
|
// ../server/src/services/vault-migration.ts
|
|
8959
|
-
import { readdir as readdir5, readFile as
|
|
8960
|
-
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";
|
|
8961
9410
|
var KEY_FILES = [".encryption.key.enc", ".runeya-verify"];
|
|
8962
9411
|
var MACHINE_FILES = [
|
|
8963
9412
|
"agents.json",
|
|
@@ -8987,17 +9436,17 @@ async function openLegacyVault(legacyRoot, masterPassword) {
|
|
|
8987
9436
|
}
|
|
8988
9437
|
async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machineKey, now) {
|
|
8989
9438
|
const stamp = now.toISOString().replace(/[:.]/g, "-").substring(0, 19);
|
|
8990
|
-
const backupDir =
|
|
8991
|
-
await
|
|
9439
|
+
const backupDir = join14(legacyRoot, `backup-${stamp}`);
|
|
9440
|
+
await mkdir10(backupDir, { recursive: true });
|
|
8992
9441
|
const jsonFiles = await vaultJsonFiles(legacyRoot);
|
|
8993
9442
|
for (const name of [...jsonFiles, ...KEY_FILES]) {
|
|
8994
|
-
if (await exists2(
|
|
8995
|
-
await copyFile2(
|
|
9443
|
+
if (await exists2(join14(legacyRoot, name))) {
|
|
9444
|
+
await copyFile2(join14(legacyRoot, name), join14(backupDir, name));
|
|
8996
9445
|
}
|
|
8997
9446
|
}
|
|
8998
9447
|
const rekeyed = [];
|
|
8999
9448
|
for (const name of jsonFiles) {
|
|
9000
|
-
const raw = await
|
|
9449
|
+
const raw = await readFile8(join14(legacyRoot, name), "utf-8");
|
|
9001
9450
|
let parsed;
|
|
9002
9451
|
try {
|
|
9003
9452
|
parsed = JSON.parse(raw);
|
|
@@ -9007,13 +9456,13 @@ async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machine
|
|
|
9007
9456
|
const counter = { n: 0 };
|
|
9008
9457
|
const converted = rekeyValue(parsed, legacyKey, machineKey, counter);
|
|
9009
9458
|
if (counter.n === 0) continue;
|
|
9010
|
-
const tmp =
|
|
9459
|
+
const tmp = join14(legacyRoot, `${name}.tmp`);
|
|
9011
9460
|
await writeFile7(tmp, JSON.stringify(converted, null, 2), "utf-8");
|
|
9012
|
-
await rename3(tmp,
|
|
9461
|
+
await rename3(tmp, join14(legacyRoot, name));
|
|
9013
9462
|
rekeyed.push({ file: name, values: counter.n });
|
|
9014
9463
|
}
|
|
9015
9464
|
for (const { file, values } of rekeyed) {
|
|
9016
|
-
const parsed = JSON.parse(await
|
|
9465
|
+
const parsed = JSON.parse(await readFile8(join14(legacyRoot, file), "utf-8"));
|
|
9017
9466
|
const check = { n: 0 };
|
|
9018
9467
|
rekeyValue(parsed, machineKey, machineKey, check);
|
|
9019
9468
|
if (check.n !== values) {
|
|
@@ -9022,13 +9471,13 @@ async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machine
|
|
|
9022
9471
|
);
|
|
9023
9472
|
}
|
|
9024
9473
|
}
|
|
9025
|
-
await
|
|
9474
|
+
await mkdir10(machineRootDir, { recursive: true });
|
|
9026
9475
|
const moved = [];
|
|
9027
9476
|
const skipped = [];
|
|
9028
9477
|
for (const name of MACHINE_FILES) {
|
|
9029
|
-
const from =
|
|
9478
|
+
const from = join14(legacyRoot, name);
|
|
9030
9479
|
if (!await exists2(from)) continue;
|
|
9031
|
-
const to =
|
|
9480
|
+
const to = join14(machineRootDir, name);
|
|
9032
9481
|
if (await exists2(to)) {
|
|
9033
9482
|
skipped.push(name);
|
|
9034
9483
|
continue;
|
|
@@ -9038,15 +9487,15 @@ async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machine
|
|
|
9038
9487
|
moved.push(name);
|
|
9039
9488
|
}
|
|
9040
9489
|
for (const name of KEY_FILES) {
|
|
9041
|
-
const path =
|
|
9490
|
+
const path = join14(legacyRoot, name);
|
|
9042
9491
|
if (await exists2(path)) await unlink(path);
|
|
9043
9492
|
}
|
|
9044
9493
|
return { backupDir, rekeyed, moved, skipped };
|
|
9045
9494
|
}
|
|
9046
9495
|
|
|
9047
9496
|
// ../server/src/services/settings-migration.ts
|
|
9048
|
-
import { readFile as
|
|
9049
|
-
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";
|
|
9050
9499
|
var SETTINGS_FILE = "settings.json";
|
|
9051
9500
|
var SETTINGS_PRIVATE_FILE = "settings.private.json";
|
|
9052
9501
|
async function exists3(path) {
|
|
@@ -9059,9 +9508,9 @@ async function legacySource(filename) {
|
|
|
9059
9508
|
return null;
|
|
9060
9509
|
}
|
|
9061
9510
|
async function moveInto(root, filename, from) {
|
|
9062
|
-
await
|
|
9063
|
-
const to =
|
|
9064
|
-
const contents = await
|
|
9511
|
+
await mkdir11(root, { recursive: true });
|
|
9512
|
+
const to = join15(root, filename);
|
|
9513
|
+
const contents = await readFile9(from, "utf-8");
|
|
9065
9514
|
const tmp = to + ".tmp";
|
|
9066
9515
|
await writeFile8(tmp, contents, "utf-8");
|
|
9067
9516
|
await rename4(tmp, to);
|
|
@@ -9085,7 +9534,7 @@ async function migrateSettingsIntoVaults() {
|
|
|
9085
9534
|
const orgId = orgs[0];
|
|
9086
9535
|
const root = orgVaultRoot(orgId);
|
|
9087
9536
|
for (const { filename, from } of sources) {
|
|
9088
|
-
if (await exists3(
|
|
9537
|
+
if (await exists3(join15(root, filename))) continue;
|
|
9089
9538
|
await moveInto(root, filename, from);
|
|
9090
9539
|
report.files.push(filename);
|
|
9091
9540
|
}
|
|
@@ -9098,17 +9547,17 @@ async function migrateSettingsIntoVaults() {
|
|
|
9098
9547
|
|
|
9099
9548
|
// ../server/src/migrations/migrate-from-legacy.ts
|
|
9100
9549
|
import { createRequire } from "module";
|
|
9101
|
-
import { randomBytes as
|
|
9102
|
-
import { randomUUID as
|
|
9550
|
+
import { randomBytes as randomBytes4, createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3 } from "crypto";
|
|
9551
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
9103
9552
|
import {
|
|
9104
|
-
readFile as
|
|
9553
|
+
readFile as readFile10,
|
|
9105
9554
|
writeFile as writeFile9,
|
|
9106
9555
|
rename as rename5,
|
|
9107
|
-
mkdir as
|
|
9556
|
+
mkdir as mkdir12,
|
|
9108
9557
|
access as access2,
|
|
9109
9558
|
readdir as readdir6
|
|
9110
9559
|
} from "fs/promises";
|
|
9111
|
-
import { join as
|
|
9560
|
+
import { join as join16, dirname as dirname9 } from "path";
|
|
9112
9561
|
var _require = createRequire(import.meta.url);
|
|
9113
9562
|
var PARSER_ID_MAP = {
|
|
9114
9563
|
"stack-monitor-parser-jsons": "native:json",
|
|
@@ -9121,7 +9570,7 @@ var PARSER_ID_MAP = {
|
|
|
9121
9570
|
function encryptValue2(plaintext) {
|
|
9122
9571
|
const key = getEncryptionKey();
|
|
9123
9572
|
if (!key) throw new Error("[legacy-migration] Encryption key not loaded");
|
|
9124
|
-
const iv =
|
|
9573
|
+
const iv = randomBytes4(16);
|
|
9125
9574
|
const cipher = createCipheriv3("aes-256-gcm", key, iv);
|
|
9126
9575
|
let enc = cipher.update(plaintext, "utf-8", "hex");
|
|
9127
9576
|
enc += cipher.final("hex");
|
|
@@ -9225,7 +9674,7 @@ async function getSodium() {
|
|
|
9225
9674
|
return _sodium;
|
|
9226
9675
|
}
|
|
9227
9676
|
async function decryptLegacyFile(filePath, key, sodium) {
|
|
9228
|
-
const raw = (await
|
|
9677
|
+
const raw = (await readFile10(filePath, "utf-8")).trim();
|
|
9229
9678
|
if (!raw || raw === "[]" || raw === "{}") {
|
|
9230
9679
|
try {
|
|
9231
9680
|
return JSON.parse(raw);
|
|
@@ -9250,7 +9699,7 @@ async function atomicWrite(path, data) {
|
|
|
9250
9699
|
}
|
|
9251
9700
|
async function runLegacyMigration(dataDir) {
|
|
9252
9701
|
const legacyRoot = env.DATA_DIR;
|
|
9253
|
-
const legacyKeyPath =
|
|
9702
|
+
const legacyKeyPath = join16(legacyRoot, "dbs", "encryption-key.json");
|
|
9254
9703
|
try {
|
|
9255
9704
|
await access2(legacyKeyPath);
|
|
9256
9705
|
} catch {
|
|
@@ -9260,18 +9709,18 @@ async function runLegacyMigration(dataDir) {
|
|
|
9260
9709
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
|
|
9261
9710
|
const legacyTmp = `${legacyRoot}-tmp`;
|
|
9262
9711
|
await rename5(legacyRoot, legacyTmp);
|
|
9263
|
-
await
|
|
9264
|
-
const legacyOld =
|
|
9712
|
+
await mkdir12(dataDir, { recursive: true });
|
|
9713
|
+
const legacyOld = join16(dataDir, `backup-${timestamp}`);
|
|
9265
9714
|
await rename5(legacyTmp, legacyOld);
|
|
9266
9715
|
console.log(`[legacy-migration] Legacy data backed up to ${legacyOld}`);
|
|
9267
|
-
const keyJson = JSON.parse(await
|
|
9716
|
+
const keyJson = JSON.parse(await readFile10(join16(legacyOld, "dbs", "encryption-key.json"), "utf-8"));
|
|
9268
9717
|
const sodium = await getSodium();
|
|
9269
9718
|
const sodiumKey = sodium.from_base64(keyJson.encryptionKey);
|
|
9270
9719
|
const keyBuffer = Buffer.from(sodiumKey);
|
|
9271
9720
|
const runyeaKeyHex = keyBuffer.toString("hex");
|
|
9272
9721
|
await setEncryptionKey(runyeaKeyHex);
|
|
9273
9722
|
console.log("[legacy-migration] Encryption key loaded in memory (temporary)");
|
|
9274
|
-
const dbsDir =
|
|
9723
|
+
const dbsDir = join16(legacyOld, "dbs");
|
|
9275
9724
|
async function readEncrypted(filePath) {
|
|
9276
9725
|
try {
|
|
9277
9726
|
return await decryptLegacyFile(filePath, sodiumKey, sodium);
|
|
@@ -9280,29 +9729,29 @@ async function runLegacyMigration(dataDir) {
|
|
|
9280
9729
|
return null;
|
|
9281
9730
|
}
|
|
9282
9731
|
}
|
|
9283
|
-
const envsDir =
|
|
9732
|
+
const envsDir = join16(dbsDir, "envs");
|
|
9284
9733
|
const envFiles = (await readdir6(envsDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
|
|
9285
9734
|
const legacyEnvs = {};
|
|
9286
9735
|
for (const file of envFiles) {
|
|
9287
9736
|
const name = file.replace(".encrypted.json", "");
|
|
9288
|
-
legacyEnvs[name] = await readEncrypted(
|
|
9737
|
+
legacyEnvs[name] = await readEncrypted(join16(envsDir, file));
|
|
9289
9738
|
}
|
|
9290
|
-
const servicesDir =
|
|
9739
|
+
const servicesDir = join16(dbsDir, "services");
|
|
9291
9740
|
const serviceFiles = (await readdir6(servicesDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
|
|
9292
9741
|
const legacyServices = {};
|
|
9293
9742
|
for (const file of serviceFiles) {
|
|
9294
9743
|
const name = file.replace(".encrypted.json", "");
|
|
9295
9744
|
if (!name) continue;
|
|
9296
|
-
const data = await readEncrypted(
|
|
9745
|
+
const data = await readEncrypted(join16(servicesDir, file));
|
|
9297
9746
|
if (data?.label) legacyServices[name] = data;
|
|
9298
9747
|
}
|
|
9299
|
-
const overridesDir =
|
|
9748
|
+
const overridesDir = join16(dbsDir, "overrides");
|
|
9300
9749
|
const overrideFiles = (await readdir6(overridesDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
|
|
9301
9750
|
const legacyServiceOverrides = {};
|
|
9302
9751
|
const legacyEnvOverrides = {};
|
|
9303
9752
|
for (const file of overrideFiles) {
|
|
9304
9753
|
const baseName = file.replace(".encrypted.json", "");
|
|
9305
|
-
const data = await readEncrypted(
|
|
9754
|
+
const data = await readEncrypted(join16(overridesDir, file));
|
|
9306
9755
|
if (!data) continue;
|
|
9307
9756
|
if (baseName.endsWith("-envs")) {
|
|
9308
9757
|
legacyServiceOverrides[baseName.slice(0, -5)] = data;
|
|
@@ -9310,18 +9759,18 @@ async function runLegacyMigration(dataDir) {
|
|
|
9310
9759
|
legacyEnvOverrides[baseName.slice(0, -12)] = data;
|
|
9311
9760
|
}
|
|
9312
9761
|
}
|
|
9313
|
-
const parsersDir =
|
|
9762
|
+
const parsersDir = join16(dbsDir, "parsers");
|
|
9314
9763
|
const parserFiles = (await readdir6(parsersDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
|
|
9315
9764
|
const legacyParsers = [];
|
|
9316
9765
|
for (const file of parserFiles) {
|
|
9317
|
-
const data = await readEncrypted(
|
|
9766
|
+
const data = await readEncrypted(join16(parsersDir, file));
|
|
9318
9767
|
if (data?.id) legacyParsers.push(data);
|
|
9319
9768
|
}
|
|
9320
|
-
const projectMonoId =
|
|
9769
|
+
const projectMonoId = randomUUID9();
|
|
9321
9770
|
const envIdMap = {};
|
|
9322
9771
|
const labelToKey = {};
|
|
9323
9772
|
for (const [name, legacyEnv] of Object.entries(legacyEnvs)) {
|
|
9324
|
-
envIdMap[name] =
|
|
9773
|
+
envIdMap[name] = randomUUID9();
|
|
9325
9774
|
if (legacyEnv?.label) labelToKey[legacyEnv.label] = name;
|
|
9326
9775
|
}
|
|
9327
9776
|
const runeEnvs = [];
|
|
@@ -9346,7 +9795,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
9346
9795
|
if (hasInvalidInterpolation(value)) continue;
|
|
9347
9796
|
const cleaned = cleanVarKey(key);
|
|
9348
9797
|
if (inheritedVars[cleaned] === value) continue;
|
|
9349
|
-
const varId =
|
|
9798
|
+
const varId = randomUUID9();
|
|
9350
9799
|
variables[varId] = makeVariable(cleaned, value);
|
|
9351
9800
|
varIdLookup.set(`${envIdMap[name]}:${cleaned}`, varId);
|
|
9352
9801
|
}
|
|
@@ -9367,7 +9816,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
9367
9816
|
const serviceIdMap = {};
|
|
9368
9817
|
const runeServices = [];
|
|
9369
9818
|
for (const [name, legacy] of Object.entries(legacyServices)) {
|
|
9370
|
-
const id =
|
|
9819
|
+
const id = randomUUID9();
|
|
9371
9820
|
serviceIdMap[name] = id;
|
|
9372
9821
|
const rootPath = legacy.rootPath || "";
|
|
9373
9822
|
const rootPathCwd = rootPath && rootPath !== "." ? rootPath : "";
|
|
@@ -9376,7 +9825,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
9376
9825
|
const parts = [cmd.spawnCmd, ...cmd.spawnArgs || []].filter(Boolean);
|
|
9377
9826
|
if (!parts.length) continue;
|
|
9378
9827
|
commands.push({
|
|
9379
|
-
id: cmd.id ||
|
|
9828
|
+
id: cmd.id || randomUUID9(),
|
|
9380
9829
|
label: "Launch",
|
|
9381
9830
|
command: parts.join(" "),
|
|
9382
9831
|
cwd: cmd.spawnOptions?.cwd || "",
|
|
@@ -9437,7 +9886,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
9437
9886
|
const legacyBootstrapCmds = container.bootstrap?.commands || [];
|
|
9438
9887
|
if (legacyBootstrapCmds.length > 0) {
|
|
9439
9888
|
dockerConfig.bootstrapCommands = legacyBootstrapCmds.filter((bs) => bs.cmd || bs.entrypoint).map((bs, idx) => ({
|
|
9440
|
-
id: bs.id ||
|
|
9889
|
+
id: bs.id || randomUUID9(),
|
|
9441
9890
|
label: bs.label || `Bootstrap ${idx + 1}`,
|
|
9442
9891
|
command: bs.cmd || "",
|
|
9443
9892
|
...bs.entrypoint ? { entrypoint: bs.entrypoint } : {},
|
|
@@ -9485,7 +9934,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
9485
9934
|
cwd: serviceCwd,
|
|
9486
9935
|
runner: dockerConfig ? "docker" : "native",
|
|
9487
9936
|
shortcuts: (legacy.shortcuts || []).filter((s) => s.spawnCmd || s.label).map((s) => ({
|
|
9488
|
-
id: s.id ||
|
|
9937
|
+
id: s.id || randomUUID9(),
|
|
9489
9938
|
label: s.label || s.spawnCmd || "",
|
|
9490
9939
|
command: [s.spawnCmd, ...s.spawnArgs || []].filter(Boolean).join(" "),
|
|
9491
9940
|
...s.spawnOptions?.cwd ? { cwd: s.spawnOptions.cwd } : {}
|
|
@@ -9525,7 +9974,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
9525
9974
|
);
|
|
9526
9975
|
if (!svcScopedEnv) {
|
|
9527
9976
|
svcScopedEnv = {
|
|
9528
|
-
id:
|
|
9977
|
+
id: randomUUID9(),
|
|
9529
9978
|
projectId: projectMonoId,
|
|
9530
9979
|
name: `${service?.name || name} (${globalEnv.name})`,
|
|
9531
9980
|
scope: "service",
|
|
@@ -9549,7 +9998,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
9549
9998
|
const varPost = typeof varData === "object" ? String(varData.suffix ?? "") : "";
|
|
9550
9999
|
if (!varKey || existingKeys.has(cleanVarKey(varKey))) continue;
|
|
9551
10000
|
const cleaned = cleanVarKey(varKey);
|
|
9552
|
-
const varId =
|
|
10001
|
+
const varId = randomUUID9();
|
|
9553
10002
|
svcScopedEnv.variables[varId] = makeVariable(cleaned, varValue, varPre, varPost);
|
|
9554
10003
|
existingKeys.add(cleaned);
|
|
9555
10004
|
}
|
|
@@ -9569,7 +10018,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
9569
10018
|
);
|
|
9570
10019
|
if (!svcScopedEnv) {
|
|
9571
10020
|
svcScopedEnv = {
|
|
9572
|
-
id:
|
|
10021
|
+
id: randomUUID9(),
|
|
9573
10022
|
projectId: projectMonoId,
|
|
9574
10023
|
name: `${service?.name || serviceName} (${globalEnv.name})`,
|
|
9575
10024
|
scope: "service",
|
|
@@ -9590,7 +10039,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
9590
10039
|
const baseEntry = Object.entries(svcScopedEnv.variables).find(([, v]) => v.key === cleaned);
|
|
9591
10040
|
let varId = baseEntry?.[0];
|
|
9592
10041
|
if (!varId) {
|
|
9593
|
-
varId =
|
|
10042
|
+
varId = randomUUID9();
|
|
9594
10043
|
svcScopedEnv.variables[varId] = {
|
|
9595
10044
|
key: cleaned,
|
|
9596
10045
|
value: { value: "", pre: "", post: "", isSecret: false, shareWithAi: false, injectIntoAi: false, required: false, overrideOnly: false, description: "" }
|
|
@@ -9620,13 +10069,13 @@ async function runLegacyMigration(dataDir) {
|
|
|
9620
10069
|
flatOverrides[varId] = makeVariable(cleaned, value);
|
|
9621
10070
|
}
|
|
9622
10071
|
} else {
|
|
9623
|
-
flatOverrides[
|
|
10072
|
+
flatOverrides[randomUUID9()] = makeVariable(cleaned, value);
|
|
9624
10073
|
}
|
|
9625
10074
|
} else {
|
|
9626
10075
|
const cleaned = cleanVarKey(key);
|
|
9627
10076
|
const existingVarId = Object.entries(env2.variables).find(([, v]) => v.key === cleaned)?.[0];
|
|
9628
10077
|
if (!existingVarId) {
|
|
9629
|
-
const varId =
|
|
10078
|
+
const varId = randomUUID9();
|
|
9630
10079
|
env2.variables[varId] = makeVariable(cleaned, value);
|
|
9631
10080
|
varIdLookup.set(`${envId}:${cleaned}`, varId);
|
|
9632
10081
|
}
|
|
@@ -9655,7 +10104,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
9655
10104
|
}
|
|
9656
10105
|
let existingSettings = {};
|
|
9657
10106
|
try {
|
|
9658
|
-
existingSettings = JSON.parse(await
|
|
10107
|
+
existingSettings = JSON.parse(await readFile10(join16(dataDir, "settings.json"), "utf-8"));
|
|
9659
10108
|
} catch {
|
|
9660
10109
|
}
|
|
9661
10110
|
const migratedParsers = legacyParsers.map((p) => ({
|
|
@@ -9676,11 +10125,11 @@ ${p.transform ?? ""}`,
|
|
|
9676
10125
|
...migratedParsers
|
|
9677
10126
|
]
|
|
9678
10127
|
};
|
|
9679
|
-
await atomicWrite(
|
|
9680
|
-
await atomicWrite(
|
|
9681
|
-
await atomicWrite(
|
|
9682
|
-
await atomicWrite(
|
|
9683
|
-
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);
|
|
9684
10133
|
const svcEnvCount = runeEnvs.filter((e) => e.scope === "service").length;
|
|
9685
10134
|
console.log(`[legacy-migration] Written: ${runeProjects.length} projects, ${runeServices.length} services, ${runeEnvs.length} environments (${svcEnvCount} service-scoped)`);
|
|
9686
10135
|
console.log(`[legacy-migration] Overrides: ${Object.keys(flatOverrides).length}, custom parsers: ${migratedParsers.length} (all disabled \u2014 rewrite required)`);
|
|
@@ -9694,9 +10143,9 @@ async function findLegacyKeyInBackup(dataDir) {
|
|
|
9694
10143
|
const entries2 = await readdir6(dataDir);
|
|
9695
10144
|
const backupDirs = entries2.filter((e) => e.startsWith("backup-")).sort((a, b) => a < b ? -1 : a > b ? 1 : 0).reverse();
|
|
9696
10145
|
for (const dir of backupDirs) {
|
|
9697
|
-
const keyJsonPath =
|
|
10146
|
+
const keyJsonPath = join16(dataDir, dir, "dbs", "encryption-key.json");
|
|
9698
10147
|
try {
|
|
9699
|
-
const raw = await
|
|
10148
|
+
const raw = await readFile10(keyJsonPath, "utf-8");
|
|
9700
10149
|
const parsed = JSON.parse(raw);
|
|
9701
10150
|
if (!parsed.encryptionKey) continue;
|
|
9702
10151
|
const sodium = await getSodium();
|
|
@@ -9714,11 +10163,11 @@ async function findLegacyKeyInBackup(dataDir) {
|
|
|
9714
10163
|
return null;
|
|
9715
10164
|
}
|
|
9716
10165
|
async function runOverrideOnlyMigration(dataDir) {
|
|
9717
|
-
const { resolve:
|
|
9718
|
-
const abDataDir =
|
|
10166
|
+
const { resolve: resolve5 } = await import("path");
|
|
10167
|
+
const abDataDir = resolve5(dataDir);
|
|
9719
10168
|
const candidateDirs = [
|
|
9720
|
-
|
|
9721
|
-
|
|
10169
|
+
join16(dirname9(env.DATA_DIR), ".runeya", "dbs", "overrides"),
|
|
10170
|
+
join16(abDataDir, "dbs", "overrides")
|
|
9722
10171
|
];
|
|
9723
10172
|
let overridesDir = null;
|
|
9724
10173
|
let overrideFiles = [];
|
|
@@ -9734,7 +10183,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
9734
10183
|
}
|
|
9735
10184
|
}
|
|
9736
10185
|
if (!overridesDir || overrideFiles.length === 0) return false;
|
|
9737
|
-
const legacyKeyPath =
|
|
10186
|
+
const legacyKeyPath = join16(overridesDir, "..", "encryption-key.json");
|
|
9738
10187
|
try {
|
|
9739
10188
|
await access2(legacyKeyPath);
|
|
9740
10189
|
return false;
|
|
@@ -9752,7 +10201,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
9752
10201
|
const envNameToId = {};
|
|
9753
10202
|
let envsRaw;
|
|
9754
10203
|
try {
|
|
9755
|
-
envsRaw = JSON.parse(await
|
|
10204
|
+
envsRaw = JSON.parse(await readFile10(join16(abDataDir, "environments.json"), "utf-8"));
|
|
9756
10205
|
for (const _env of envsRaw) {
|
|
9757
10206
|
const env2 = _env;
|
|
9758
10207
|
if (env2["name"] && env2["id"]) {
|
|
@@ -9771,7 +10220,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
9771
10220
|
let servicesRaw = [];
|
|
9772
10221
|
const serviceNameToId = {};
|
|
9773
10222
|
try {
|
|
9774
|
-
servicesRaw = JSON.parse(await
|
|
10223
|
+
servicesRaw = JSON.parse(await readFile10(join16(abDataDir, "services.json"), "utf-8"));
|
|
9775
10224
|
for (const _svc of servicesRaw) {
|
|
9776
10225
|
const svc = _svc;
|
|
9777
10226
|
if (svc["name"] && svc["id"]) serviceNameToId[svc["name"]] = svc["id"];
|
|
@@ -9788,7 +10237,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
9788
10237
|
const envName = baseName.slice(0, -"-environment".length);
|
|
9789
10238
|
let data;
|
|
9790
10239
|
try {
|
|
9791
|
-
data = await decryptLegacyFile(
|
|
10240
|
+
data = await decryptLegacyFile(join16(overridesDir, file), sodiumKey, sodium);
|
|
9792
10241
|
} catch (err) {
|
|
9793
10242
|
console.warn(`[override-migration] Failed to decrypt ${file}: ${err.message}`);
|
|
9794
10243
|
continue;
|
|
@@ -9810,12 +10259,12 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
9810
10259
|
if (isSameAsBase(value, baseVar)) continue;
|
|
9811
10260
|
flatOverrides[varId] = makeVariable(cleaned, value);
|
|
9812
10261
|
} else {
|
|
9813
|
-
flatOverrides[
|
|
10262
|
+
flatOverrides[randomUUID9()] = makeVariable(cleaned, value);
|
|
9814
10263
|
}
|
|
9815
10264
|
}
|
|
9816
10265
|
}
|
|
9817
10266
|
if (Object.keys(flatOverrides).length > 0) {
|
|
9818
|
-
await atomicWrite(
|
|
10267
|
+
await atomicWrite(join16(abDataDir, "environment-overrides.json"), flatOverrides);
|
|
9819
10268
|
console.log(`[override-migration] Written ${Object.keys(flatOverrides).length} override(s) to environment-overrides.json`);
|
|
9820
10269
|
}
|
|
9821
10270
|
let envsModified = false;
|
|
@@ -9825,7 +10274,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
9825
10274
|
const serviceName = baseName.slice(0, -"-envs".length);
|
|
9826
10275
|
let data;
|
|
9827
10276
|
try {
|
|
9828
|
-
data = await decryptLegacyFile(
|
|
10277
|
+
data = await decryptLegacyFile(join16(overridesDir, file), sodiumKey, sodium);
|
|
9829
10278
|
} catch (err) {
|
|
9830
10279
|
console.warn(`[override-migration] Failed to decrypt ${file}: ${err.message}`);
|
|
9831
10280
|
continue;
|
|
@@ -9847,7 +10296,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
9847
10296
|
);
|
|
9848
10297
|
if (!svcScopedEnv) {
|
|
9849
10298
|
svcScopedEnv = {
|
|
9850
|
-
id:
|
|
10299
|
+
id: randomUUID9(),
|
|
9851
10300
|
projectId: globalEnv["projectId"],
|
|
9852
10301
|
name: `${service?.["name"] || serviceName} (${globalEnv["name"]})`,
|
|
9853
10302
|
scope: "service",
|
|
@@ -9869,7 +10318,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
9869
10318
|
const baseEntry = Object.entries(svcScopedEnv["variables"]).find(([, v]) => v.key === cleaned);
|
|
9870
10319
|
let varId = baseEntry?.[0];
|
|
9871
10320
|
if (!varId) {
|
|
9872
|
-
varId =
|
|
10321
|
+
varId = randomUUID9();
|
|
9873
10322
|
svcScopedEnv["variables"][varId] = {
|
|
9874
10323
|
key: cleaned,
|
|
9875
10324
|
value: { value: "", pre: "", post: "", isSecret: false, shareWithAi: false, injectIntoAi: false, required: false, overrideOnly: false, description: "" }
|
|
@@ -9884,7 +10333,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
9884
10333
|
}
|
|
9885
10334
|
}
|
|
9886
10335
|
if (envsModified) {
|
|
9887
|
-
await atomicWrite(
|
|
10336
|
+
await atomicWrite(join16(abDataDir, "environments.json"), envsRaw);
|
|
9888
10337
|
console.log("[override-migration] Merged service overrides into environments.json");
|
|
9889
10338
|
}
|
|
9890
10339
|
const migratedDir = overridesDir.replace(/overrides$/, "overrides-migrated");
|
|
@@ -9899,7 +10348,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
9899
10348
|
}
|
|
9900
10349
|
|
|
9901
10350
|
// ../server/src/services/server-state.ts
|
|
9902
|
-
import { join as
|
|
10351
|
+
import { join as join17 } from "path";
|
|
9903
10352
|
import { stat as stat10 } from "fs/promises";
|
|
9904
10353
|
var ENCRYPTED_KEY_FILENAME2 = ".encryption.key.enc";
|
|
9905
10354
|
var VERIFY_FILENAME2 = ".runeya-verify";
|
|
@@ -9909,22 +10358,22 @@ async function getServerState() {
|
|
|
9909
10358
|
let hasVerify = false;
|
|
9910
10359
|
let hasData = false;
|
|
9911
10360
|
try {
|
|
9912
|
-
await stat10(
|
|
10361
|
+
await stat10(join17(dataDir, ENCRYPTED_KEY_FILENAME2));
|
|
9913
10362
|
hasEncFile = true;
|
|
9914
10363
|
} catch {
|
|
9915
10364
|
}
|
|
9916
10365
|
try {
|
|
9917
|
-
await stat10(
|
|
10366
|
+
await stat10(join17(dataDir, VERIFY_FILENAME2));
|
|
9918
10367
|
hasVerify = true;
|
|
9919
10368
|
} catch {
|
|
9920
10369
|
}
|
|
9921
10370
|
try {
|
|
9922
|
-
await stat10(
|
|
10371
|
+
await stat10(join17(dataDir, "projects.json"));
|
|
9923
10372
|
hasData = true;
|
|
9924
10373
|
} catch {
|
|
9925
10374
|
}
|
|
9926
10375
|
try {
|
|
9927
|
-
await stat10(
|
|
10376
|
+
await stat10(join17(dataDir, "project.json"));
|
|
9928
10377
|
hasData = true;
|
|
9929
10378
|
} catch {
|
|
9930
10379
|
}
|
|
@@ -10230,7 +10679,7 @@ function getOpenApiDocument() {
|
|
|
10230
10679
|
|
|
10231
10680
|
// ../server/src/create-server.ts
|
|
10232
10681
|
import rateLimit from "express-rate-limit";
|
|
10233
|
-
import { existsSync as
|
|
10682
|
+
import { existsSync as existsSync3, createReadStream } from "fs";
|
|
10234
10683
|
import multer from "multer";
|
|
10235
10684
|
|
|
10236
10685
|
// ../server/src/services/image-cleanup.service.ts
|
|
@@ -10327,6 +10776,7 @@ var llmsLimiter = rateLimit({
|
|
|
10327
10776
|
async function createLocalServer() {
|
|
10328
10777
|
installLogCapture();
|
|
10329
10778
|
generateJwtSecret();
|
|
10779
|
+
setMcpClientCheck((clientId) => mcpPairing.isPaired(clientId));
|
|
10330
10780
|
const ciMode = await loadFromEnv();
|
|
10331
10781
|
await runLegacyMigration(env.DATA_DIR);
|
|
10332
10782
|
const keyDir = await keyRoot();
|
|
@@ -10345,10 +10795,10 @@ async function createLocalServer() {
|
|
|
10345
10795
|
console.log("[server] Premier lancement \u2014 setup requis");
|
|
10346
10796
|
}
|
|
10347
10797
|
}
|
|
10348
|
-
const absoluteDataDir =
|
|
10798
|
+
const absoluteDataDir = resolve4(env.DATA_DIR);
|
|
10349
10799
|
console.log(`[server] Local project directory: ${absoluteDataDir}`);
|
|
10350
10800
|
console.log(`[server] Machine directory: ${machineRoot()}`);
|
|
10351
|
-
if (
|
|
10801
|
+
if (existsSync3(env.DATA_DIR)) {
|
|
10352
10802
|
await ensureDataGitignore(env.DATA_DIR);
|
|
10353
10803
|
}
|
|
10354
10804
|
await settingsManager.getSettings();
|
|
@@ -10436,7 +10886,7 @@ async function createLocalServer() {
|
|
|
10436
10886
|
machineRoot(),
|
|
10437
10887
|
...orgRoots,
|
|
10438
10888
|
...vaultRoots,
|
|
10439
|
-
...[machineRoot(), ...vaultRoots].map((root) =>
|
|
10889
|
+
...[machineRoot(), ...vaultRoots].map((root) => join18(root, CONVERSATIONS_DIRNAME))
|
|
10440
10890
|
]);
|
|
10441
10891
|
if (removed > 0) console.log(`[server] ${removed} fichier(s) temporaire(s) orphelin(s) retir\xE9(s)`);
|
|
10442
10892
|
})().catch((err) => console.warn("[server] Temp file sweep failed:", err));
|
|
@@ -10697,13 +11147,14 @@ async function createLocalServer() {
|
|
|
10697
11147
|
app.post("/api/auth/refresh", refreshLimiter, async (req, res) => {
|
|
10698
11148
|
const auth = req.headers.authorization;
|
|
10699
11149
|
const jwt = typeof auth === "string" && auth.startsWith("Bearer ") ? auth.slice(7) : "";
|
|
11150
|
+
let mcpClientId;
|
|
10700
11151
|
try {
|
|
10701
|
-
await verifyJwt(jwt);
|
|
11152
|
+
({ mcpClientId } = await verifyJwt(jwt, { allowMcp: true }));
|
|
10702
11153
|
} catch {
|
|
10703
11154
|
res.status(401).json({ error: "Session invalide" });
|
|
10704
11155
|
return;
|
|
10705
11156
|
}
|
|
10706
|
-
res.json({ token: await signJwt() });
|
|
11157
|
+
res.json({ token: await signJwt(mcpClientId ? { mcpClientId, ttl: MCP_TOKEN_TTL } : {}) });
|
|
10707
11158
|
});
|
|
10708
11159
|
app.post("/api/unlock", authLimiter, csrfMiddleware, express.json({ limit: "10kb" }), async (req, res) => {
|
|
10709
11160
|
try {
|
|
@@ -10761,8 +11212,8 @@ async function createLocalServer() {
|
|
|
10761
11212
|
}
|
|
10762
11213
|
encKey = Buffer.from(encryptionKeyHex, "hex");
|
|
10763
11214
|
} else {
|
|
10764
|
-
const { randomBytes:
|
|
10765
|
-
encKey =
|
|
11215
|
+
const { randomBytes: randomBytes5 } = await import("crypto");
|
|
11216
|
+
encKey = randomBytes5(32);
|
|
10766
11217
|
}
|
|
10767
11218
|
}
|
|
10768
11219
|
await writeEncryptedKey(await keyRoot(), masterPassword, encKey);
|
|
@@ -11084,16 +11535,16 @@ async function createLocalServer() {
|
|
|
11084
11535
|
]);
|
|
11085
11536
|
server.closeAllConnections();
|
|
11086
11537
|
await Promise.race([
|
|
11087
|
-
new Promise((
|
|
11088
|
-
new Promise((
|
|
11538
|
+
new Promise((resolve5) => wss.close(() => resolve5())),
|
|
11539
|
+
new Promise((resolve5) => setTimeout(resolve5, 2e3))
|
|
11089
11540
|
]);
|
|
11090
11541
|
await Promise.race([
|
|
11091
|
-
new Promise((
|
|
11092
|
-
new Promise((
|
|
11542
|
+
new Promise((resolve5) => server.close(() => resolve5())),
|
|
11543
|
+
new Promise((resolve5) => setTimeout(resolve5, 2e3))
|
|
11093
11544
|
]);
|
|
11094
11545
|
console.log("[server] Shutdown complete");
|
|
11095
11546
|
};
|
|
11096
|
-
const { initCompanion } = await import("./companion-
|
|
11547
|
+
const { initCompanion } = await import("./companion-W3UMZWGH.js");
|
|
11097
11548
|
initCompanion();
|
|
11098
11549
|
return { app, server, wss, shutdown };
|
|
11099
11550
|
}
|
|
@@ -11112,7 +11563,7 @@ var PullEnvError = class extends Error {
|
|
|
11112
11563
|
};
|
|
11113
11564
|
async function promptPassword() {
|
|
11114
11565
|
process.stderr.write("Master password: ");
|
|
11115
|
-
return new Promise((
|
|
11566
|
+
return new Promise((resolve5) => {
|
|
11116
11567
|
const stdin = process.stdin;
|
|
11117
11568
|
const wasRaw = stdin.isRaw;
|
|
11118
11569
|
if (stdin.isTTY) stdin.setRawMode(true);
|
|
@@ -11125,7 +11576,7 @@ async function promptPassword() {
|
|
|
11125
11576
|
stdin.pause();
|
|
11126
11577
|
stdin.removeListener("data", onData);
|
|
11127
11578
|
process.stderr.write("\n");
|
|
11128
|
-
|
|
11579
|
+
resolve5(input);
|
|
11129
11580
|
} else if (ch === "") {
|
|
11130
11581
|
process.exit(1);
|
|
11131
11582
|
} else if (ch === "\x7F" || ch === "\b") {
|
|
@@ -11223,8 +11674,8 @@ function onTrayServicesChanged(listener) {
|
|
|
11223
11674
|
}
|
|
11224
11675
|
|
|
11225
11676
|
// ../server/src/services/tray-icon.ts
|
|
11226
|
-
import { mkdir as
|
|
11227
|
-
import { join as
|
|
11677
|
+
import { mkdir as mkdir13, writeFile as writeFile10, rename as rename6 } from "fs/promises";
|
|
11678
|
+
import { join as join19 } from "path";
|
|
11228
11679
|
|
|
11229
11680
|
// ../server/src/services/png-to-ico.ts
|
|
11230
11681
|
function isPng(bytes) {
|
|
@@ -11384,7 +11835,7 @@ var FETCH_TIMEOUT_MS = 3e3;
|
|
|
11384
11835
|
var CACHE_TTL_MS = 10 * 6e4;
|
|
11385
11836
|
var cache2 = /* @__PURE__ */ new Map();
|
|
11386
11837
|
function trayIconDir() {
|
|
11387
|
-
return
|
|
11838
|
+
return join19(machineRoot(), "tray-icons");
|
|
11388
11839
|
}
|
|
11389
11840
|
async function resolveTrayIcon(platform = process.platform) {
|
|
11390
11841
|
const orgId = getCurrentProject()?.orgId;
|
|
@@ -11412,8 +11863,8 @@ async function renderThemedIcon(platform) {
|
|
|
11412
11863
|
}
|
|
11413
11864
|
async function writeTrayIcon(stem, png, platform) {
|
|
11414
11865
|
const dir = trayIconDir();
|
|
11415
|
-
await
|
|
11416
|
-
const path =
|
|
11866
|
+
await mkdir13(dir, { recursive: true });
|
|
11867
|
+
const path = join19(dir, stem);
|
|
11417
11868
|
if (platform === "win32") {
|
|
11418
11869
|
const ico = pngToIco(png);
|
|
11419
11870
|
return ico ? writeAtomic(`${path}.ico`, ico) : null;
|
|
@@ -11455,7 +11906,7 @@ if (process.env["RUNEYA_DATA_DIR"]) {
|
|
|
11455
11906
|
console.log(`[server] Working directory: ${resolved} (from RUNEYA_DATA_DIR)`);
|
|
11456
11907
|
}
|
|
11457
11908
|
function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500) {
|
|
11458
|
-
return new Promise((
|
|
11909
|
+
return new Promise((resolve5, reject) => {
|
|
11459
11910
|
let attempts = 0;
|
|
11460
11911
|
const wssErrorHandler = () => {
|
|
11461
11912
|
};
|
|
@@ -11476,7 +11927,7 @@ function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500)
|
|
|
11476
11927
|
server.listen(port, host, () => {
|
|
11477
11928
|
server.removeListener("error", onError);
|
|
11478
11929
|
wss?.removeListener("error", wssErrorHandler);
|
|
11479
|
-
|
|
11930
|
+
resolve5();
|
|
11480
11931
|
});
|
|
11481
11932
|
};
|
|
11482
11933
|
tryListen();
|
|
@@ -11489,6 +11940,7 @@ async function main() {
|
|
|
11489
11940
|
console.log("Hello World 2");
|
|
11490
11941
|
const { server, wss, shutdown } = await createLocalServer();
|
|
11491
11942
|
await listenWithRetry(server, env.PORT, env.HOST, wss);
|
|
11943
|
+
await registerInstance();
|
|
11492
11944
|
console.log(`[server] Local server listening on ${env.HOST}:${env.PORT}`);
|
|
11493
11945
|
console.log(`[server] tRPC HTTP: http://${env.HOST}:${env.PORT}/api/trpc`);
|
|
11494
11946
|
console.log(`[server] tRPC WS: ws://${env.HOST}:${env.PORT}`);
|
|
@@ -11548,4 +12000,4 @@ export {
|
|
|
11548
12000
|
stopProcess,
|
|
11549
12001
|
userPrefix
|
|
11550
12002
|
};
|
|
11551
|
-
//# sourceMappingURL=src-
|
|
12003
|
+
//# sourceMappingURL=src-LEDGW5KP.js.map
|