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