@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,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
settingsManager
|
|
3
|
-
} from "./chunk-FE53XX4M.js";
|
|
4
1
|
import {
|
|
5
2
|
COMPANION_SIGNATURE,
|
|
3
|
+
PASTED_TASK_LEAD,
|
|
4
|
+
PTY_MCP_READY_MAX_MS,
|
|
6
5
|
activeSessionRegistry,
|
|
7
6
|
claudeSessionExists,
|
|
8
7
|
companionCreateDefaults,
|
|
@@ -11,7 +10,6 @@ import {
|
|
|
11
10
|
companionSkips,
|
|
12
11
|
companionSystemNote,
|
|
13
12
|
companionThinkingIds,
|
|
14
|
-
conversationSectionStore,
|
|
15
13
|
conversationStore,
|
|
16
14
|
deliverToMainConversation,
|
|
17
15
|
findClaudeSessionFile,
|
|
@@ -27,7 +25,7 @@ import {
|
|
|
27
25
|
noteSpawnConversation,
|
|
28
26
|
observePtyChunk,
|
|
29
27
|
onCompanionThinkingChanged,
|
|
30
|
-
|
|
28
|
+
pasteToPty,
|
|
31
29
|
readClaudeTranscript,
|
|
32
30
|
resetRelaunches,
|
|
33
31
|
resolveAgentCwd,
|
|
@@ -37,31 +35,54 @@ import {
|
|
|
37
35
|
signJwt,
|
|
38
36
|
stopCompanion,
|
|
39
37
|
syncConversationFromClaudeTranscript,
|
|
40
|
-
verifyJwt
|
|
41
|
-
|
|
38
|
+
verifyJwt,
|
|
39
|
+
waitForPtyInput
|
|
40
|
+
} from "./chunk-JNK6H5KH.js";
|
|
41
|
+
import {
|
|
42
|
+
RUNEYA_CAPABILITIES,
|
|
43
|
+
applyProjectAnchor,
|
|
44
|
+
buildAnthropicTools,
|
|
45
|
+
buildOpenAITools,
|
|
46
|
+
buildToolSafetyInstruction,
|
|
47
|
+
runCapability,
|
|
48
|
+
toRawParams
|
|
49
|
+
} from "./chunk-6IBPK6UE.js";
|
|
50
|
+
import {
|
|
51
|
+
conversationSectionStore,
|
|
52
|
+
projectStore
|
|
53
|
+
} from "./chunk-7QXP4FXD.js";
|
|
54
|
+
import {
|
|
55
|
+
describeVariables,
|
|
56
|
+
environmentOverridesStore,
|
|
57
|
+
environmentResolver,
|
|
58
|
+
filterEnvForAi
|
|
59
|
+
} from "./chunk-WDHLEO5S.js";
|
|
42
60
|
import {
|
|
43
|
-
StoreDiskCoordinator,
|
|
44
61
|
agentManager,
|
|
45
|
-
agentStore
|
|
62
|
+
agentStore
|
|
63
|
+
} from "./chunk-5XG3X2TM.js";
|
|
64
|
+
import {
|
|
65
|
+
serviceStore
|
|
66
|
+
} from "./chunk-4D3GCBPO.js";
|
|
67
|
+
import {
|
|
68
|
+
StoreDiskCoordinator,
|
|
46
69
|
decryptValue,
|
|
47
|
-
decryptVariableSlots,
|
|
48
70
|
encryptValue,
|
|
49
|
-
encryptVariableSlots,
|
|
50
71
|
ensureProjectsLoaded,
|
|
51
72
|
environmentStore,
|
|
52
|
-
extractEncryptedVariableSlots,
|
|
53
|
-
getEncryptionKey,
|
|
54
73
|
hasEncryptionKey,
|
|
55
74
|
isCiMode,
|
|
56
75
|
isEncryptedValue,
|
|
57
76
|
recordLocalRemoval,
|
|
58
|
-
refsOfProjectId
|
|
59
|
-
|
|
60
|
-
} from "./chunk-QF56Z6LI.js";
|
|
77
|
+
refsOfProjectId
|
|
78
|
+
} from "./chunk-DFPZ4MTI.js";
|
|
61
79
|
import {
|
|
62
80
|
isCliAvailable,
|
|
63
81
|
resolveCliInvocation
|
|
64
82
|
} from "./chunk-OZBSOSAS.js";
|
|
83
|
+
import {
|
|
84
|
+
settingsManager
|
|
85
|
+
} from "./chunk-NPMSBYXC.js";
|
|
65
86
|
import {
|
|
66
87
|
atomicWriteJsonSecure,
|
|
67
88
|
atomicWriteSecure,
|
|
@@ -72,41 +93,33 @@ import {
|
|
|
72
93
|
renameWithRetry,
|
|
73
94
|
resolveConversationRunDir,
|
|
74
95
|
wasRead
|
|
75
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-4LJQIQMN.js";
|
|
76
97
|
import {
|
|
77
|
-
allVaultRoots,
|
|
78
98
|
env,
|
|
79
99
|
keyedFilePath,
|
|
80
100
|
legacyMachineFilePath,
|
|
81
101
|
listOrgVaults,
|
|
82
102
|
machineFilePath,
|
|
83
103
|
machineRoot,
|
|
84
|
-
orgVaultRoot
|
|
85
|
-
|
|
86
|
-
} from "./chunk-KS7A5CTW.js";
|
|
104
|
+
orgVaultRoot
|
|
105
|
+
} from "./chunk-VPSQJOSN.js";
|
|
87
106
|
import {
|
|
88
107
|
AGENT_STOP_FETCH_TIMEOUT,
|
|
89
108
|
ApiKeysFileSchema,
|
|
90
|
-
BUILTIN_VARIABLES,
|
|
91
|
-
BootstrapCommandSchema,
|
|
92
109
|
CLAUDE_CODE_MODELS,
|
|
93
110
|
DockerConfigSchema,
|
|
94
111
|
HealthCheckSchema,
|
|
95
112
|
JSLogParserSchema,
|
|
96
113
|
LogSourceConfigSchema,
|
|
97
|
-
MASKED_VALUE,
|
|
98
114
|
MissingVariableSchema,
|
|
99
115
|
RestartStrategySchema,
|
|
100
116
|
ScenarioSchema,
|
|
101
117
|
ServiceCommandSchema,
|
|
102
|
-
ServiceConfigSchema,
|
|
103
118
|
ServiceShortcutSchema,
|
|
104
119
|
ambiguousServiceSlugs,
|
|
105
120
|
crossServiceValues,
|
|
106
121
|
findUnresolvedReferences,
|
|
107
122
|
interpolateBuiltinsInEnv,
|
|
108
|
-
isSharedWithAi,
|
|
109
|
-
maskLogSourceSecrets,
|
|
110
123
|
nativeParsers,
|
|
111
124
|
restoreLogSourceSecrets,
|
|
112
125
|
servicePortValues,
|
|
@@ -117,10 +130,11 @@ import {
|
|
|
117
130
|
} from "./chunk-MZSDTA4A.js";
|
|
118
131
|
|
|
119
132
|
// ../server/src/trpc/routers/chat.ts
|
|
120
|
-
import { z as
|
|
133
|
+
import { z as z6 } from "zod";
|
|
121
134
|
import Anthropic2 from "@anthropic-ai/sdk";
|
|
122
135
|
import OpenAI2 from "openai";
|
|
123
136
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
137
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
124
138
|
|
|
125
139
|
// ../../packages/mcp-server/src/launch.ts
|
|
126
140
|
import { fileURLToPath } from "url";
|
|
@@ -152,7 +166,8 @@ function pickEnv(env2) {
|
|
|
152
166
|
...env2.RUNEYA_BASE_URL ? { RUNEYA_BASE_URL: env2.RUNEYA_BASE_URL } : {},
|
|
153
167
|
...env2.RUNEYA_API_TOKEN ? { RUNEYA_API_TOKEN: env2.RUNEYA_API_TOKEN } : {},
|
|
154
168
|
...env2.RUNEYA_PROJECT_ID ? { RUNEYA_PROJECT_ID: env2.RUNEYA_PROJECT_ID } : {},
|
|
155
|
-
...env2.RUNEYA_ORG_ID ? { RUNEYA_ORG_ID: env2.RUNEYA_ORG_ID } : {}
|
|
169
|
+
...env2.RUNEYA_ORG_ID ? { RUNEYA_ORG_ID: env2.RUNEYA_ORG_ID } : {},
|
|
170
|
+
...env2.RUNEYA_MCP_READY_ID ? { RUNEYA_MCP_READY_ID: env2.RUNEYA_MCP_READY_ID } : {}
|
|
156
171
|
};
|
|
157
172
|
}
|
|
158
173
|
function runeyaMcpConfig(env2) {
|
|
@@ -178,6 +193,16 @@ function codexMcpArgs(env2) {
|
|
|
178
193
|
}
|
|
179
194
|
return ["-c", `mcp_servers.runeya={${fields.join(",")}}`];
|
|
180
195
|
}
|
|
196
|
+
function codexRuneyaMcpArgs(env2, build = codexMcpArgs, warn = console.warn) {
|
|
197
|
+
const disabled = ["-c", "mcp_servers.runeya.enabled=false"];
|
|
198
|
+
if (!env2.RUNEYA_API_TOKEN) return disabled;
|
|
199
|
+
try {
|
|
200
|
+
return build(env2);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
warn(`[codex] Failed to resolve MCP server path: ${err.message}`);
|
|
203
|
+
return disabled;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
181
206
|
function tomlString(value) {
|
|
182
207
|
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/[\u0000-\u001f\u007f]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
183
208
|
return `"${escaped}"`;
|
|
@@ -500,7 +525,8 @@ var router = t.router;
|
|
|
500
525
|
var createCallerFactory = t.createCallerFactory;
|
|
501
526
|
var publicProcedure = t.procedure;
|
|
502
527
|
var API_KEY_REGEX = /^[0-9a-f]{64}$/i;
|
|
503
|
-
var
|
|
528
|
+
var MCP_CLIENT_ROUTES = /* @__PURE__ */ new Set([...RUNEYA_CAPABILITIES.map((c) => c.route), "project.containing"]);
|
|
529
|
+
var authMiddleware = t.middleware(async ({ ctx, next, path }) => {
|
|
504
530
|
const { authorization } = ctx;
|
|
505
531
|
if (authorization?.startsWith("Bearer ") && API_KEY_REGEX.test(authorization.slice(7))) {
|
|
506
532
|
if (!hasEncryptionKey()) {
|
|
@@ -520,15 +546,24 @@ var authMiddleware = t.middleware(async ({ ctx, next }) => {
|
|
|
520
546
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Missing or invalid Authorization header" });
|
|
521
547
|
}
|
|
522
548
|
const token = authorization.slice(7);
|
|
549
|
+
let mcpClientId = null;
|
|
523
550
|
try {
|
|
524
|
-
await verifyJwt(token);
|
|
551
|
+
mcpClientId = (await verifyJwt(token, { allowMcp: true })).mcpClientId ?? null;
|
|
525
552
|
} catch {
|
|
526
553
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Invalid or expired token" });
|
|
527
554
|
}
|
|
528
|
-
|
|
555
|
+
if (mcpClientId && !MCP_CLIENT_ROUTES.has(path)) {
|
|
556
|
+
throw new TRPCError({ code: "FORBIDDEN", message: "An MCP client cannot call this route." });
|
|
557
|
+
}
|
|
558
|
+
return next({ ctx: { ...ctx, callerType: "app", apiKey: null, mcpClientId } });
|
|
529
559
|
});
|
|
530
560
|
var protectedProcedure = t.procedure.use(authMiddleware);
|
|
531
561
|
var LOOPBACK = /* @__PURE__ */ new Set(["127.0.0.1", "::1", "::ffff:127.0.0.1"]);
|
|
562
|
+
function requireLoopback(ctx) {
|
|
563
|
+
if (!ctx.remoteAddress || !LOOPBACK.has(ctx.remoteAddress)) {
|
|
564
|
+
throw new TRPCError({ code: "FORBIDDEN", message: "This route only answers the local machine." });
|
|
565
|
+
}
|
|
566
|
+
}
|
|
532
567
|
async function requireLoopbackOrToken(ctx) {
|
|
533
568
|
if (ctx.remoteAddress && LOOPBACK.has(ctx.remoteAddress)) return;
|
|
534
569
|
if (isCiMode()) return;
|
|
@@ -545,6 +580,11 @@ async function requireLoopbackOrToken(ctx) {
|
|
|
545
580
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Invalid or expired token" });
|
|
546
581
|
}
|
|
547
582
|
}
|
|
583
|
+
function requireHuman(ctx) {
|
|
584
|
+
if (isCiMode() || ctx.callerType !== "app" || ctx.mcpClientId) {
|
|
585
|
+
throw new TRPCError({ code: "FORBIDDEN", message: "Only the Runeya app can do this." });
|
|
586
|
+
}
|
|
587
|
+
}
|
|
548
588
|
function requireServiceAction(ctx, serviceId, action) {
|
|
549
589
|
if (ctx.callerType !== "api") return;
|
|
550
590
|
const perm = ctx.apiKey.scopes.services.find((p) => p.serviceId === serviceId);
|
|
@@ -591,1790 +631,6 @@ var ShortString200Schema = z2.string().max(200);
|
|
|
591
631
|
var DescriptionSchema = z2.string().max(2e3);
|
|
592
632
|
var OptionalProviderIdSchema = Id128Schema.optional();
|
|
593
633
|
|
|
594
|
-
// ../server/src/services/environment-overrides-store.ts
|
|
595
|
-
import { readFile as readFile2, writeFile, mkdir, chmod } from "fs/promises";
|
|
596
|
-
import { dirname, join as join3 } from "path";
|
|
597
|
-
var FILENAME = "environment-overrides.json";
|
|
598
|
-
var EnvironmentOverridesStore = class {
|
|
599
|
-
data = {};
|
|
600
|
-
loaded = false;
|
|
601
|
-
/**
|
|
602
|
-
* Le coordinateur partagé par les stores : un seul rechargement à la fois,
|
|
603
|
-
* jamais pendant une écriture, et une substitution atomique à la fin.
|
|
604
|
-
*
|
|
605
|
-
* Ce store faisait sa propre cuisine et se marquait « chargé » avant ses
|
|
606
|
-
* `await` : toute lecture concurrente repartait avec une table vide — une
|
|
607
|
-
* surcharge déjà écrite paraissait absente, et l'assistant réclamait au
|
|
608
|
-
* déverrouillage des variables que ce poste avait déjà renseignées.
|
|
609
|
-
*/
|
|
610
|
-
disk = new StoreDiskCoordinator();
|
|
611
|
-
/** Ce que le disque portait au dernier chargement (cf. `load`). */
|
|
612
|
-
fingerprint = "";
|
|
613
|
-
diskEncryptedSlots = {};
|
|
614
|
-
/**
|
|
615
|
-
* Les surcharges dont le secret ne s'est pas ouvert.
|
|
616
|
-
*
|
|
617
|
-
* Vides en mémoire, elles doivent repartir chiffrées : sans cela, la
|
|
618
|
-
* sauvegarde suivante remplacerait par du blanc une valeur dont il ne
|
|
619
|
-
* manquait que la clé.
|
|
620
|
-
*/
|
|
621
|
-
unreadableSecrets = /* @__PURE__ */ new Set();
|
|
622
|
-
/** Which vault each override was read from, until its project is known. */
|
|
623
|
-
roots = /* @__PURE__ */ new Map();
|
|
624
|
-
/**
|
|
625
|
-
* An override belongs to the project whose variable it answers.
|
|
626
|
-
*
|
|
627
|
-
* Nothing in an override says so — they are keyed by variable id alone — so
|
|
628
|
-
* the owning project is resolved through the environment that defines the
|
|
629
|
-
* variable. Overrides are never synced: they stay in the project's vault on
|
|
630
|
-
* this machine, and no other machine sees them.
|
|
631
|
-
*
|
|
632
|
-
* An override whose variable no longer exists (a deleted environment, a row
|
|
633
|
-
* left behind) has no project to belong to. It stays wherever it was read,
|
|
634
|
-
* falling back to the key's own directory — never dropped, since the variable
|
|
635
|
-
* may simply not be loaded yet.
|
|
636
|
-
*/
|
|
637
|
-
async rootFor(variableId) {
|
|
638
|
-
const root = await environmentStore.rootOfVariable(variableId);
|
|
639
|
-
if (root) return root;
|
|
640
|
-
return this.roots.get(variableId) ?? dirname(await keyedFilePath(FILENAME));
|
|
641
|
-
}
|
|
642
|
-
/** Where earlier versions kept them: read once, rewritten to their vaults. */
|
|
643
|
-
getLegacyFilePath() {
|
|
644
|
-
return legacyMachineFilePath(FILENAME);
|
|
645
|
-
}
|
|
646
|
-
/** Les fichiers qui portent des surcharges : les coffres, et l'emplacement d'avant. */
|
|
647
|
-
async sourcePaths() {
|
|
648
|
-
const roots = await allVaultRoots();
|
|
649
|
-
return [
|
|
650
|
-
this.getLegacyFilePath(),
|
|
651
|
-
await keyedFilePath(FILENAME),
|
|
652
|
-
...roots.map((root) => join3(root, FILENAME))
|
|
653
|
-
];
|
|
654
|
-
}
|
|
655
|
-
/**
|
|
656
|
-
* Charger, et recharger quand les fichiers ont bougé sous nos pieds.
|
|
657
|
-
*
|
|
658
|
-
* L'empreinte se prend **après** `ensureProjectsLoaded` : la liste des coffres
|
|
659
|
-
* en dépend, et une empreinte prise sur une liste incomplète ne ressemblerait
|
|
660
|
-
* à aucune des suivantes — le store se rechargerait à chaque lecture.
|
|
661
|
-
*/
|
|
662
|
-
async load() {
|
|
663
|
-
await this.disk.load(async () => {
|
|
664
|
-
if (!getEncryptionKey()) return null;
|
|
665
|
-
await ensureProjectsLoaded();
|
|
666
|
-
const fingerprint = await fingerprintFiles(await this.sourcePaths());
|
|
667
|
-
if (this.loaded && fingerprint === this.fingerprint) return null;
|
|
668
|
-
const next = await this.readVaults();
|
|
669
|
-
return () => {
|
|
670
|
-
this.data = next.data;
|
|
671
|
-
this.diskEncryptedSlots = next.diskEncryptedSlots;
|
|
672
|
-
this.roots = next.roots;
|
|
673
|
-
this.unreadableSecrets = next.unreadableSecrets;
|
|
674
|
-
this.fingerprint = fingerprint;
|
|
675
|
-
this.loaded = true;
|
|
676
|
-
};
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
|
-
/**
|
|
680
|
-
* Read every vault, then the places older versions used, into fresh tables
|
|
681
|
-
* that `load` swaps in at once.
|
|
682
|
-
*
|
|
683
|
-
* Variable ids are unique, so merging the files cannot collide; a vault read
|
|
684
|
-
* later simply wins over a legacy copy of the same override.
|
|
685
|
-
*/
|
|
686
|
-
async readVaults() {
|
|
687
|
-
const data = {};
|
|
688
|
-
const diskEncryptedSlots = {};
|
|
689
|
-
const roots = /* @__PURE__ */ new Map();
|
|
690
|
-
const unreadableSecrets = /* @__PURE__ */ new Set();
|
|
691
|
-
for (const path of await this.sourcePaths()) {
|
|
692
|
-
let parsed;
|
|
693
|
-
try {
|
|
694
|
-
parsed = JSON.parse(await readFile2(path, "utf-8"));
|
|
695
|
-
} catch {
|
|
696
|
-
continue;
|
|
697
|
-
}
|
|
698
|
-
Object.assign(
|
|
699
|
-
diskEncryptedSlots,
|
|
700
|
-
extractEncryptedVariableSlots(parsed)
|
|
701
|
-
);
|
|
702
|
-
const decrypted = decryptVariableSlots(
|
|
703
|
-
parsed,
|
|
704
|
-
"environment-overrides",
|
|
705
|
-
(variableId) => unreadableSecrets.add(variableId),
|
|
706
|
-
vaultIdOfRoot(dirname(path))
|
|
707
|
-
);
|
|
708
|
-
for (const [id, override] of Object.entries(decrypted)) {
|
|
709
|
-
data[id] = { value: override.value };
|
|
710
|
-
roots.set(id, dirname(path));
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
return { data, diskEncryptedSlots, roots, unreadableSecrets };
|
|
714
|
-
}
|
|
715
|
-
save() {
|
|
716
|
-
return this.disk.save(() => this.doSave());
|
|
717
|
-
}
|
|
718
|
-
/**
|
|
719
|
-
* Write each vault that holds — or held — an override.
|
|
720
|
-
*
|
|
721
|
-
* A vault emptied by this save is still written, as an empty object: an
|
|
722
|
-
* override that moved to its project's vault must not survive in the one it
|
|
723
|
-
* came from.
|
|
724
|
-
*/
|
|
725
|
-
async doSave() {
|
|
726
|
-
const byRoot = /* @__PURE__ */ new Map();
|
|
727
|
-
for (const root of this.roots.values()) byRoot.set(root, {});
|
|
728
|
-
for (const [variableId, override] of Object.entries(this.data)) {
|
|
729
|
-
const root = await this.rootFor(variableId);
|
|
730
|
-
this.roots.set(variableId, root);
|
|
731
|
-
const bucket = byRoot.get(root) ?? {};
|
|
732
|
-
bucket[variableId] = override;
|
|
733
|
-
byRoot.set(root, bucket);
|
|
734
|
-
}
|
|
735
|
-
for (const [root, overrides] of byRoot) {
|
|
736
|
-
const encrypted = this.keepUnreadableSecrets(encryptVariableSlots(
|
|
737
|
-
overrides,
|
|
738
|
-
this.diskEncryptedSlots,
|
|
739
|
-
vaultIdOfRoot(root)
|
|
740
|
-
));
|
|
741
|
-
Object.assign(
|
|
742
|
-
this.diskEncryptedSlots,
|
|
743
|
-
extractEncryptedVariableSlots(encrypted)
|
|
744
|
-
);
|
|
745
|
-
await mkdir(root, { recursive: true });
|
|
746
|
-
const path = join3(root, FILENAME);
|
|
747
|
-
const tmpPath = path + ".tmp";
|
|
748
|
-
await writeFile(tmpPath, JSON.stringify(encrypted, null, 2), "utf-8");
|
|
749
|
-
await renameWithRetry(tmpPath, path);
|
|
750
|
-
await chmod(path, 384);
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
/**
|
|
754
|
-
* Rendre au disque le chiffré d'une surcharge illisible, plutôt qu'un blanc.
|
|
755
|
-
*
|
|
756
|
-
* Pendant du garde-fou du store des environnements : tant que la valeur en
|
|
757
|
-
* mémoire est vide, c'est que personne n'a retouché la variable et que son
|
|
758
|
-
* chiffré fait toujours foi.
|
|
759
|
-
*/
|
|
760
|
-
keepUnreadableSecrets(overrides) {
|
|
761
|
-
if (this.unreadableSecrets.size === 0) return overrides;
|
|
762
|
-
const result = { ...overrides };
|
|
763
|
-
for (const variableId of this.unreadableSecrets) {
|
|
764
|
-
const ciphertext = this.diskEncryptedSlots[variableId];
|
|
765
|
-
const override = result[variableId];
|
|
766
|
-
if (!ciphertext || !override || override.value.value) continue;
|
|
767
|
-
result[variableId] = { ...override, value: { ...override.value, value: ciphertext } };
|
|
768
|
-
}
|
|
769
|
-
return result;
|
|
770
|
-
}
|
|
771
|
-
async getOverride(variableId) {
|
|
772
|
-
await this.load();
|
|
773
|
-
return this.data[variableId] ?? null;
|
|
774
|
-
}
|
|
775
|
-
async setOverride(variableId, override) {
|
|
776
|
-
await this.load();
|
|
777
|
-
this.data[variableId] = { value: override.value };
|
|
778
|
-
await this.save();
|
|
779
|
-
}
|
|
780
|
-
async removeOverride(variableId) {
|
|
781
|
-
await this.load();
|
|
782
|
-
if (!this.data[variableId]) return;
|
|
783
|
-
delete this.data[variableId];
|
|
784
|
-
this.unreadableSecrets.delete(variableId);
|
|
785
|
-
await this.save();
|
|
786
|
-
}
|
|
787
|
-
async getAll() {
|
|
788
|
-
await this.load();
|
|
789
|
-
return { ...this.data };
|
|
790
|
-
}
|
|
791
|
-
async removeOverridesForVariables(variableIds) {
|
|
792
|
-
await this.load();
|
|
793
|
-
let changed = false;
|
|
794
|
-
for (const id of variableIds) {
|
|
795
|
-
if (this.data[id]) {
|
|
796
|
-
delete this.data[id];
|
|
797
|
-
this.unreadableSecrets.delete(id);
|
|
798
|
-
changed = true;
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
if (changed) await this.save();
|
|
802
|
-
}
|
|
803
|
-
};
|
|
804
|
-
var environmentOverridesStore = new EnvironmentOverridesStore();
|
|
805
|
-
|
|
806
|
-
// ../server/src/services/environment-resolver.ts
|
|
807
|
-
function interpolateValue(raw, envContext) {
|
|
808
|
-
return raw.replace(/\{\{environment\.([^}]+)\}\}/g, (match, key) => {
|
|
809
|
-
return envContext[key] !== void 0 ? envContext[key] : match;
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
|
-
function resolveVariable(variable, envContext) {
|
|
813
|
-
const slot = variable.value;
|
|
814
|
-
const finalValue = interpolateValue(slot.pre, envContext) + interpolateValue(slot.value, envContext) + interpolateValue(slot.post, envContext);
|
|
815
|
-
return { value: finalValue, isSecret: slot.isSecret };
|
|
816
|
-
}
|
|
817
|
-
function slotReferenceText(variable) {
|
|
818
|
-
const slot = variable.value;
|
|
819
|
-
return `${slot.pre}
|
|
820
|
-
${slot.value}
|
|
821
|
-
${slot.post}`;
|
|
822
|
-
}
|
|
823
|
-
var HIDDEN_VALUES_NOTICE = `Values shown as ${MASKED_VALUE} are secrets their author did not share with AI. The service does receive them: never treat such a key as missing, and never pass ${MASKED_VALUE} as its value.`;
|
|
824
|
-
function filterEnvForAi(resolved, sharedKeys) {
|
|
825
|
-
const secret = new Set(resolved.envSecrets);
|
|
826
|
-
const shared = new Set(sharedKeys);
|
|
827
|
-
const env2 = {};
|
|
828
|
-
const envSecrets = [];
|
|
829
|
-
const hiddenKeys = [];
|
|
830
|
-
for (const [key, value] of Object.entries(resolved.env)) {
|
|
831
|
-
const hidden = secret.has(key) && !shared.has(key);
|
|
832
|
-
env2[key] = hidden ? MASKED_VALUE : value;
|
|
833
|
-
if (secret.has(key)) envSecrets.push(key);
|
|
834
|
-
if (hidden) hiddenKeys.push(key);
|
|
835
|
-
}
|
|
836
|
-
return {
|
|
837
|
-
env: env2,
|
|
838
|
-
envSecrets,
|
|
839
|
-
hiddenKeys,
|
|
840
|
-
...hiddenKeys.length > 0 ? { hiddenNotice: HIDDEN_VALUES_NOTICE } : {}
|
|
841
|
-
};
|
|
842
|
-
}
|
|
843
|
-
function describeVariables(environments, ctx) {
|
|
844
|
-
const details = [];
|
|
845
|
-
for (const environment of environments) {
|
|
846
|
-
for (const [variableId, variable] of Object.entries(environment.variables ?? {})) {
|
|
847
|
-
const slot = variable.value;
|
|
848
|
-
const isOverridden = ctx.overriddenVariableIds.has(variableId);
|
|
849
|
-
const isSecret = slot.isSecret || ctx.resolvedSecretKeys.has(variable.key);
|
|
850
|
-
const readableByAi = ctx.maskedKeys.has(variable.key) ? false : !isSecret || isSharedWithAi(slot);
|
|
851
|
-
details.push({
|
|
852
|
-
key: variable.key,
|
|
853
|
-
inService: ctx.serviceKeys.has(variable.key),
|
|
854
|
-
environmentId: environment.id,
|
|
855
|
-
environmentName: environment.name,
|
|
856
|
-
scope: environment.scope,
|
|
857
|
-
...environment.scope === "service" && environment.reference ? { environmentServiceId: environment.reference } : {},
|
|
858
|
-
...slot.description ? { description: slot.description } : {},
|
|
859
|
-
secret: isSecret,
|
|
860
|
-
readableByAi,
|
|
861
|
-
injectedIntoAi: slot.injectIntoAi,
|
|
862
|
-
required: slot.required,
|
|
863
|
-
overrideOnly: slot.overrideOnly,
|
|
864
|
-
overridden: isOverridden,
|
|
865
|
-
...isOverridden && ctx.sharedValues[variable.key] !== void 0 ? { sharedValue: ctx.sharedValues[variable.key] } : {}
|
|
866
|
-
});
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
return details.sort((a, b) => a.key.localeCompare(b.key) || a.environmentName.localeCompare(b.environmentName));
|
|
870
|
-
}
|
|
871
|
-
function withOverriddenValue(variable, override) {
|
|
872
|
-
return {
|
|
873
|
-
...variable,
|
|
874
|
-
value: {
|
|
875
|
-
...variable.value,
|
|
876
|
-
value: override.value,
|
|
877
|
-
pre: override.pre,
|
|
878
|
-
post: override.post
|
|
879
|
-
}
|
|
880
|
-
};
|
|
881
|
-
}
|
|
882
|
-
var EnvironmentResolver = class {
|
|
883
|
-
async resolveForService(ctx, opts) {
|
|
884
|
-
if (!ctx.activeEnvironmentId) {
|
|
885
|
-
return { env: {}, envSecrets: [] };
|
|
886
|
-
}
|
|
887
|
-
const serviceEnv = await environmentStore.findServiceEnvironment(ctx.serviceId, ctx.activeEnvironmentId);
|
|
888
|
-
if (!serviceEnv) {
|
|
889
|
-
return this.resolveEnvironment(ctx.activeEnvironmentId, opts);
|
|
890
|
-
}
|
|
891
|
-
return this.resolveEnvironment(serviceEnv.id, opts);
|
|
892
|
-
}
|
|
893
|
-
/**
|
|
894
|
-
* `applyOverrides: false` rend ce que l'équipe partage, sans ce que ce poste
|
|
895
|
-
* a mis par-dessus : c'est la réponse à « qu'est-ce que ma surcharge
|
|
896
|
-
* remplace ». Le défaut reste `true` — tout le reste du produit veut la
|
|
897
|
-
* valeur avec laquelle le service tourne réellement.
|
|
898
|
-
*/
|
|
899
|
-
async resolveEnvironment(environmentId, opts) {
|
|
900
|
-
const visited = /* @__PURE__ */ new Set();
|
|
901
|
-
const result = await this.resolveDag(environmentId, visited);
|
|
902
|
-
const overridesByKey = opts?.applyOverrides === false ? {} : await this.resolveOverridesByKey(environmentId);
|
|
903
|
-
for (const [varId, variable] of Object.entries(result.variables)) {
|
|
904
|
-
const override = overridesByKey[variable.key];
|
|
905
|
-
if (override) {
|
|
906
|
-
result.variables[varId] = withOverriddenValue(variable, override.variable.value);
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
const rawEnv = {};
|
|
910
|
-
for (const [_varId, variable] of Object.entries(result.variables)) {
|
|
911
|
-
const slot = variable.value;
|
|
912
|
-
rawEnv[variable.key] = slot.pre + slot.value + slot.post;
|
|
913
|
-
}
|
|
914
|
-
const { ctx: interpolationCtx, secretKeys: ctxSecretKeys } = await this.buildInterpolationContext(environmentId, rawEnv, result.parentEnv, result.parentSecretKeys, result.parentKeySources);
|
|
915
|
-
const secretKeys = this.collectSecretKeys(result.variables, ctxSecretKeys);
|
|
916
|
-
const env2 = {};
|
|
917
|
-
const envSecrets = [];
|
|
918
|
-
for (const [_varId, variable] of Object.entries(result.variables)) {
|
|
919
|
-
const { value, isSecret } = resolveVariable(variable, interpolationCtx);
|
|
920
|
-
env2[variable.key] = value;
|
|
921
|
-
if (isSecret || secretKeys.has(variable.key)) {
|
|
922
|
-
envSecrets.push(variable.key);
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
return { env: env2, envSecrets };
|
|
926
|
-
}
|
|
927
|
-
/**
|
|
928
|
-
* Resolve an environment (with inheritance) and return only the variables
|
|
929
|
-
* flagged `injectIntoAi`, as a flat KEY→value dict ready to be injected into
|
|
930
|
-
* the process environment of an AI agent spawn.
|
|
931
|
-
*/
|
|
932
|
-
async resolveAiInjectedEnv(environmentId) {
|
|
933
|
-
if (!environmentId) return {};
|
|
934
|
-
const visited = /* @__PURE__ */ new Set();
|
|
935
|
-
const result = await this.resolveDag(environmentId, visited);
|
|
936
|
-
const overridesByKey = await this.resolveOverridesByKey(environmentId);
|
|
937
|
-
for (const [varId, variable] of Object.entries(result.variables)) {
|
|
938
|
-
const override = overridesByKey[variable.key];
|
|
939
|
-
if (override) {
|
|
940
|
-
result.variables[varId] = withOverriddenValue(variable, override.variable.value);
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
const rawEnv = {};
|
|
944
|
-
for (const variable of Object.values(result.variables)) {
|
|
945
|
-
const slot = variable.value;
|
|
946
|
-
rawEnv[variable.key] = slot.pre + slot.value + slot.post;
|
|
947
|
-
}
|
|
948
|
-
const { ctx: interpolationCtx } = await this.buildInterpolationContext(
|
|
949
|
-
environmentId,
|
|
950
|
-
rawEnv,
|
|
951
|
-
result.parentEnv,
|
|
952
|
-
result.parentSecretKeys,
|
|
953
|
-
result.parentKeySources
|
|
954
|
-
);
|
|
955
|
-
const injected = {};
|
|
956
|
-
for (const variable of Object.values(result.variables)) {
|
|
957
|
-
if (!variable.value.injectIntoAi) continue;
|
|
958
|
-
const { value } = resolveVariable(variable, interpolationCtx);
|
|
959
|
-
injected[variable.key] = value;
|
|
960
|
-
}
|
|
961
|
-
return injected;
|
|
962
|
-
}
|
|
963
|
-
/**
|
|
964
|
-
* Les variables qu'il faut remplir avant qu'un service de cet environnement
|
|
965
|
-
* puisse démarrer.
|
|
966
|
-
*
|
|
967
|
-
* Deux motifs, et ils ne se mesurent pas au même endroit :
|
|
968
|
-
*
|
|
969
|
-
* - `required` porte sur la valeur **finale**, interpolation et surcharge
|
|
970
|
-
* comprises : une variable obligatoire dont la valeur ne tient qu'à une
|
|
971
|
-
* référence morte est aussi vide qu'une case blanche.
|
|
972
|
-
* - `overrideOnly` porte sur l'**existence d'une surcharge machine**. Elle
|
|
973
|
-
* n'a par construction pas de valeur partagée ; ce qui manque n'est pas un
|
|
974
|
-
* contenu, c'est la réponse de ce poste-ci.
|
|
975
|
-
*
|
|
976
|
-
* Les drapeaux se lisent sur la définition, jamais sur le slot résolu : une
|
|
977
|
-
* surcharge remplace le slot entier et ne porte aucun drapeau — les y lire
|
|
978
|
-
* rendrait toute variable surchargée silencieusement facultative.
|
|
979
|
-
*/
|
|
980
|
-
async findMissingVariables(environmentId) {
|
|
981
|
-
if (!environmentId) return [];
|
|
982
|
-
const [{ env: env2 }, definitions, overridesByKey] = await Promise.all([
|
|
983
|
-
this.resolveEnvironment(environmentId),
|
|
984
|
-
this.resolveDag(environmentId, /* @__PURE__ */ new Set()),
|
|
985
|
-
this.resolveOverridesByKey(environmentId)
|
|
986
|
-
]);
|
|
987
|
-
const missing = [];
|
|
988
|
-
for (const [variableId, variable] of Object.entries(definitions.variables)) {
|
|
989
|
-
const slot = variable.value;
|
|
990
|
-
const source = definitions.sources[variableId];
|
|
991
|
-
const base = {
|
|
992
|
-
variableId,
|
|
993
|
-
key: variable.key,
|
|
994
|
-
description: slot.description,
|
|
995
|
-
isSecret: slot.isSecret,
|
|
996
|
-
environmentId: source?.environmentId ?? environmentId,
|
|
997
|
-
environmentName: source?.environmentName ?? ""
|
|
998
|
-
};
|
|
999
|
-
if (slot.overrideOnly && !overridesByKey[variable.key]) {
|
|
1000
|
-
missing.push({ ...base, reason: "overrideOnly" });
|
|
1001
|
-
continue;
|
|
1002
|
-
}
|
|
1003
|
-
if (slot.required && (env2[variable.key] ?? "") === "") {
|
|
1004
|
-
missing.push({ ...base, reason: "required" });
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
return missing;
|
|
1008
|
-
}
|
|
1009
|
-
/**
|
|
1010
|
-
* Tout ce qu'il reste à remplir sous l'environnement courant.
|
|
1011
|
-
*
|
|
1012
|
-
* Deux périmètres, et il faut les deux :
|
|
1013
|
-
*
|
|
1014
|
-
* 1. **L'environnement courant lui-même**, héritage compris —
|
|
1015
|
-
* `findMissingVariables` remonte ses `parentIds`. C'est là que vivent les
|
|
1016
|
-
* variables globales du projet, et c'est la première branche balayée.
|
|
1017
|
-
* 2. **Les environnements de service qui s'activent avec lui**
|
|
1018
|
-
* (`activateOn`). Ceux-là ne sont pas des ancêtres du global : ils en
|
|
1019
|
-
* héritent, dans l'autre sens. Ne pas y descendre revenait à ne rien
|
|
1020
|
-
* réclamer d'un service jusqu'à ce qu'on essaie de le démarrer.
|
|
1021
|
-
*
|
|
1022
|
-
* Dédoublonné par identifiant de variable : une variable du global remonte
|
|
1023
|
-
* dans chaque branche de service qui en hérite, et l'assistant ne doit pas
|
|
1024
|
-
* demander deux fois la même chose.
|
|
1025
|
-
*/
|
|
1026
|
-
async findMissingForActiveEnvironment(globalEnvId) {
|
|
1027
|
-
if (!globalEnvId) return [];
|
|
1028
|
-
const serviceEnvs = await environmentStore.listByActivateOn(globalEnvId);
|
|
1029
|
-
const branches = await Promise.all([
|
|
1030
|
-
this.findMissingVariables(globalEnvId),
|
|
1031
|
-
...serviceEnvs.map((env2) => this.findMissingVariables(env2.id))
|
|
1032
|
-
]);
|
|
1033
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1034
|
-
const missing = [];
|
|
1035
|
-
for (const branch of branches) {
|
|
1036
|
-
for (const item of branch) {
|
|
1037
|
-
if (seen.has(item.variableId)) continue;
|
|
1038
|
-
seen.add(item.variableId);
|
|
1039
|
-
missing.push(item);
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
return missing;
|
|
1043
|
-
}
|
|
1044
|
-
/**
|
|
1045
|
-
* Les clés que leur auteur a partagées avec les IA, héritage compris.
|
|
1046
|
-
*
|
|
1047
|
-
* Le partage se lit sur la **définition**, et seulement là : une surcharge
|
|
1048
|
-
* machine ne dit que la valeur de ce poste (`withOverriddenValue`). Il n'y a
|
|
1049
|
-
* donc qu'un endroit où ce drapeau existe, et rien à réconcilier.
|
|
1050
|
-
*
|
|
1051
|
-
* Des clés, pas des valeurs : la valeur rendue reste celle que le service
|
|
1052
|
-
* reçoit, surcharges et interpolation comprises.
|
|
1053
|
-
*/
|
|
1054
|
-
async resolveAiSharedKeys(environmentId) {
|
|
1055
|
-
if (!environmentId) return /* @__PURE__ */ new Set();
|
|
1056
|
-
const result = await this.resolveDag(environmentId, /* @__PURE__ */ new Set());
|
|
1057
|
-
const keys = /* @__PURE__ */ new Set();
|
|
1058
|
-
for (const variable of Object.values(result.variables)) {
|
|
1059
|
-
if (isSharedWithAi(variable.value)) keys.add(variable.key);
|
|
1060
|
-
}
|
|
1061
|
-
return keys;
|
|
1062
|
-
}
|
|
1063
|
-
/**
|
|
1064
|
-
* Idem, pour ce qu'un service verra réellement : son environnement dédié
|
|
1065
|
-
* quand il en a un, l'environnement global sinon — la règle de sélection de
|
|
1066
|
-
* `resolveForService`.
|
|
1067
|
-
*/
|
|
1068
|
-
async resolveAiSharedKeysForService(ctx) {
|
|
1069
|
-
if (!ctx.activeEnvironmentId) return /* @__PURE__ */ new Set();
|
|
1070
|
-
const serviceEnv = await environmentStore.findServiceEnvironment(ctx.serviceId, ctx.activeEnvironmentId);
|
|
1071
|
-
return this.resolveAiSharedKeys(serviceEnv ? serviceEnv.id : ctx.activeEnvironmentId);
|
|
1072
|
-
}
|
|
1073
|
-
/**
|
|
1074
|
-
* Idem, pour ce qu'un service verra réellement : son environnement dédié
|
|
1075
|
-
* quand il en a un, l'environnement global sinon. Miroir de
|
|
1076
|
-
* `resolveForService`, dont il partage la règle de sélection.
|
|
1077
|
-
*/
|
|
1078
|
-
async findMissingForService(ctx) {
|
|
1079
|
-
if (!ctx.activeEnvironmentId) return [];
|
|
1080
|
-
const serviceEnv = await environmentStore.findServiceEnvironment(ctx.serviceId, ctx.activeEnvironmentId);
|
|
1081
|
-
return this.findMissingVariables(serviceEnv ? serviceEnv.id : ctx.activeEnvironmentId);
|
|
1082
|
-
}
|
|
1083
|
-
async resolveEnvironmentWithSources(environmentId) {
|
|
1084
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1085
|
-
const result = await this.resolveDag(environmentId, visited);
|
|
1086
|
-
const overridesByKey = await this.resolveOverridesByKey(environmentId);
|
|
1087
|
-
for (const [varId, variable] of Object.entries(result.variables)) {
|
|
1088
|
-
const override = overridesByKey[variable.key];
|
|
1089
|
-
if (override) {
|
|
1090
|
-
result.variables[varId] = withOverriddenValue(variable, override.variable.value);
|
|
1091
|
-
result.sources[varId] = {
|
|
1092
|
-
...result.sources[varId],
|
|
1093
|
-
overridden: true,
|
|
1094
|
-
...override.source ? { overriddenBy: override.source } : {}
|
|
1095
|
-
};
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
const rawEnv = {};
|
|
1099
|
-
for (const [_varId, variable] of Object.entries(result.variables)) {
|
|
1100
|
-
const slot = variable.value;
|
|
1101
|
-
rawEnv[variable.key] = slot.pre + slot.value + slot.post;
|
|
1102
|
-
}
|
|
1103
|
-
const { ctx: interpolationCtx, secretKeys: ctxSecretKeys, keySources } = await this.buildInterpolationContext(environmentId, rawEnv, result.parentEnv, result.parentSecretKeys, result.parentKeySources);
|
|
1104
|
-
const secretKeys = this.collectSecretKeys(result.variables, ctxSecretKeys);
|
|
1105
|
-
const env2 = {};
|
|
1106
|
-
const envSecrets = [];
|
|
1107
|
-
for (const [_varId, variable] of Object.entries(result.variables)) {
|
|
1108
|
-
const { value, isSecret } = resolveVariable(variable, interpolationCtx);
|
|
1109
|
-
env2[variable.key] = value;
|
|
1110
|
-
if (isSecret || secretKeys.has(variable.key)) {
|
|
1111
|
-
envSecrets.push(variable.key);
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
const refPattern = /^\{\{environment\.([^}]+)\}\}$/;
|
|
1115
|
-
for (const [varId, variable] of Object.entries(result.variables)) {
|
|
1116
|
-
const match = variable.value.value.match(refPattern);
|
|
1117
|
-
if (match) {
|
|
1118
|
-
const refSource = keySources[match[1]];
|
|
1119
|
-
if (refSource && result.sources[varId]?.environmentId !== refSource.environmentId) {
|
|
1120
|
-
result.sources[varId] = {
|
|
1121
|
-
...result.sources[varId],
|
|
1122
|
-
resolvedFrom: refSource
|
|
1123
|
-
};
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
return {
|
|
1128
|
-
variables: result.variables,
|
|
1129
|
-
env: env2,
|
|
1130
|
-
envSecrets,
|
|
1131
|
-
sources: result.sources
|
|
1132
|
-
};
|
|
1133
|
-
}
|
|
1134
|
-
/** Check if a raw value string interpolates any key that is secret in the context */
|
|
1135
|
-
/**
|
|
1136
|
-
* Toutes les clés dont la valeur est, directement ou par interpolation, un
|
|
1137
|
-
* secret.
|
|
1138
|
-
*
|
|
1139
|
-
* Le contexte d'interpolation ne connaît que les secrets *hérités* : les
|
|
1140
|
-
* secrets définis dans le même environnement n'y figurent pas, si bien qu'un
|
|
1141
|
-
* `AUTH='Bearer {{environment.TOKEN}}'` voisin de son propre `TOKEN` secret
|
|
1142
|
-
* ressortait non marqué — et donc non masqué par toute la chaîne en aval.
|
|
1143
|
-
*
|
|
1144
|
-
* On part des secrets hérités plus ceux déclarés ici, puis on propage
|
|
1145
|
-
* jusqu'au point fixe : une valeur qui référence une clé secrète devient
|
|
1146
|
-
* secrète à son tour, y compris en chaîne (A secret → B cite A → C cite B).
|
|
1147
|
-
*/
|
|
1148
|
-
collectSecretKeys(variables, parentSecretKeys) {
|
|
1149
|
-
const secretKeys = new Set(parentSecretKeys);
|
|
1150
|
-
for (const variable of Object.values(variables)) {
|
|
1151
|
-
if (variable.value.isSecret) secretKeys.add(variable.key);
|
|
1152
|
-
}
|
|
1153
|
-
let changed = true;
|
|
1154
|
-
while (changed) {
|
|
1155
|
-
changed = false;
|
|
1156
|
-
for (const variable of Object.values(variables)) {
|
|
1157
|
-
if (secretKeys.has(variable.key)) continue;
|
|
1158
|
-
if (this.interpolatesSecretKey(slotReferenceText(variable), secretKeys)) {
|
|
1159
|
-
secretKeys.add(variable.key);
|
|
1160
|
-
changed = true;
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
return secretKeys;
|
|
1165
|
-
}
|
|
1166
|
-
interpolatesSecretKey(rawValue, ctxSecretKeys) {
|
|
1167
|
-
const refs2 = [...rawValue.matchAll(/\{\{environment\.([^}]+)\}\}/g)].map((m) => m[1]);
|
|
1168
|
-
return refs2.some((ref) => ctxSecretKeys.has(ref));
|
|
1169
|
-
}
|
|
1170
|
-
/**
|
|
1171
|
-
* Build the interpolation context for an environment.
|
|
1172
|
-
*
|
|
1173
|
-
* For inheriting envs (has parents): start from parent concrete values,
|
|
1174
|
-
* then overlay with current concrete values. This ensures that
|
|
1175
|
-
* {{environment.KEY}} resolves against the parent's value when the child
|
|
1176
|
-
* redefines KEY as an interpolation reference.
|
|
1177
|
-
*
|
|
1178
|
-
* For isolated service envs (scope=service, activateOn set, no parents):
|
|
1179
|
-
* the global env is resolved and used as fallback.
|
|
1180
|
-
*/
|
|
1181
|
-
async buildInterpolationContext(environmentId, rawEnv, parentEnv, parentSecretKeys, parentKeySources) {
|
|
1182
|
-
const environment = await environmentStore.get(environmentId);
|
|
1183
|
-
if (environment && environment.scope === "service" && environment.activateOn && (!environment.parentIds || environment.parentIds.length === 0)) {
|
|
1184
|
-
const globalResolved = await this.resolveEnvironmentWithSources(environment.activateOn);
|
|
1185
|
-
const secretKeys = new Set(globalResolved.envSecrets);
|
|
1186
|
-
const keySources = {};
|
|
1187
|
-
for (const [varId, variable] of Object.entries(globalResolved.variables)) {
|
|
1188
|
-
const source = globalResolved.sources[varId];
|
|
1189
|
-
if (source) {
|
|
1190
|
-
keySources[variable.key] = { environmentId: source.environmentId, environmentName: source.environmentName };
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
const ctx2 = { ...globalResolved.env };
|
|
1194
|
-
for (const [key, value] of Object.entries(rawEnv)) {
|
|
1195
|
-
if (!/\{\{environment\.[^}]+\}\}/.test(value)) {
|
|
1196
|
-
ctx2[key] = value;
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
return { ctx: ctx2, secretKeys, keySources };
|
|
1200
|
-
}
|
|
1201
|
-
const ctx = { ...parentEnv };
|
|
1202
|
-
for (const [key, value] of Object.entries(rawEnv)) {
|
|
1203
|
-
if (!/\{\{environment\.[^}]+\}\}/.test(value)) {
|
|
1204
|
-
ctx[key] = value;
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
return { ctx, secretKeys: parentSecretKeys, keySources: parentKeySources };
|
|
1208
|
-
}
|
|
1209
|
-
/**
|
|
1210
|
-
* For service-scoped envs, compute effective parentIds by also including
|
|
1211
|
-
* the service-scoped envs of ancestor global environments.
|
|
1212
|
-
*
|
|
1213
|
-
* Example: "Server (test)" has parentIds=[test-global]. test-global has
|
|
1214
|
-
* parentIds=[Local-global]. This method adds "Server (Local)" to the
|
|
1215
|
-
* effective parents so service-specific variables from ancestor envs
|
|
1216
|
-
* are inherited correctly.
|
|
1217
|
-
*/
|
|
1218
|
-
async getEffectiveParentIds(environment) {
|
|
1219
|
-
if (environment.scope !== "service" || !environment.reference || !environment.activateOn) {
|
|
1220
|
-
return environment.parentIds;
|
|
1221
|
-
}
|
|
1222
|
-
const additionalIds = [];
|
|
1223
|
-
for (const parentId of environment.parentIds) {
|
|
1224
|
-
const parentEnv = await environmentStore.get(parentId);
|
|
1225
|
-
if (!parentEnv || parentEnv.scope !== "global") continue;
|
|
1226
|
-
for (const grandparentGlobalId of parentEnv.parentIds) {
|
|
1227
|
-
const ancestorServiceEnv = await environmentStore.findServiceEnvironment(environment.reference, grandparentGlobalId);
|
|
1228
|
-
if (ancestorServiceEnv && !environment.parentIds.includes(ancestorServiceEnv.id)) {
|
|
1229
|
-
additionalIds.push(ancestorServiceEnv.id);
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
if (additionalIds.length === 0) return environment.parentIds;
|
|
1234
|
-
return [...environment.parentIds, ...additionalIds];
|
|
1235
|
-
}
|
|
1236
|
-
/**
|
|
1237
|
-
* Project an environment's variables onto their overrides: keep only the
|
|
1238
|
-
* variables that have one, and swap in the override's value slot. The key
|
|
1239
|
-
* always comes from the definition.
|
|
1240
|
-
*
|
|
1241
|
-
* Feeding this projection back through `resolveDag` resolves the override
|
|
1242
|
-
* chain with the exact same inheritance and dedup-by-key rules as the value
|
|
1243
|
-
* chain — so an override set on a parent env is no longer swallowed when a
|
|
1244
|
-
* child (or a service env) redeclares the same key.
|
|
1245
|
-
*/
|
|
1246
|
-
projectOverrides(variables, overrides) {
|
|
1247
|
-
const projected = {};
|
|
1248
|
-
for (const [varId, variable] of Object.entries(variables)) {
|
|
1249
|
-
const override = overrides[varId];
|
|
1250
|
-
if (override) {
|
|
1251
|
-
projected[varId] = { key: variable.key, value: override.value };
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
return projected;
|
|
1255
|
-
}
|
|
1256
|
-
/**
|
|
1257
|
-
* Resolve the override chain independently of the value chain and return the
|
|
1258
|
-
* winning override per variable key (empty when nothing is overridden).
|
|
1259
|
-
*/
|
|
1260
|
-
async resolveOverridesByKey(environmentId) {
|
|
1261
|
-
const allOverrides = await environmentOverridesStore.getAll();
|
|
1262
|
-
if (Object.keys(allOverrides).length === 0) return {};
|
|
1263
|
-
const result = await this.resolveDag(
|
|
1264
|
-
environmentId,
|
|
1265
|
-
/* @__PURE__ */ new Set(),
|
|
1266
|
-
(variables) => this.projectOverrides(variables, allOverrides)
|
|
1267
|
-
);
|
|
1268
|
-
const byKey = {};
|
|
1269
|
-
for (const [varId, variable] of Object.entries(result.variables)) {
|
|
1270
|
-
const source = result.sources[varId];
|
|
1271
|
-
byKey[variable.key] = {
|
|
1272
|
-
variable,
|
|
1273
|
-
...source ? { source: { environmentId: source.environmentId, environmentName: source.environmentName } } : {}
|
|
1274
|
-
};
|
|
1275
|
-
}
|
|
1276
|
-
return byKey;
|
|
1277
|
-
}
|
|
1278
|
-
async resolveDag(environmentId, visited, project) {
|
|
1279
|
-
if (visited.has(environmentId)) {
|
|
1280
|
-
throw new Error(`Circular dependency detected in environment inheritance for ${environmentId}`);
|
|
1281
|
-
}
|
|
1282
|
-
const environment = await environmentStore.get(environmentId);
|
|
1283
|
-
if (!environment) {
|
|
1284
|
-
throw new Error(`Environment "${environmentId}" not found`);
|
|
1285
|
-
}
|
|
1286
|
-
visited.add(environmentId);
|
|
1287
|
-
const effectiveParentIds = await this.getEffectiveParentIds(environment);
|
|
1288
|
-
const parentResults = [];
|
|
1289
|
-
for (const parentId of effectiveParentIds) {
|
|
1290
|
-
const parentVisited = new Set(visited);
|
|
1291
|
-
const parentResult = await this.resolveDag(parentId, parentVisited, project);
|
|
1292
|
-
parentResults.push(parentResult);
|
|
1293
|
-
}
|
|
1294
|
-
const mergedVariables = {};
|
|
1295
|
-
const sources = {};
|
|
1296
|
-
for (const result of parentResults) {
|
|
1297
|
-
for (const [varId, variable] of Object.entries(result.variables)) {
|
|
1298
|
-
for (const [existingId, existingVar] of Object.entries(mergedVariables)) {
|
|
1299
|
-
if (existingVar.key === variable.key && existingId !== varId) {
|
|
1300
|
-
delete mergedVariables[existingId];
|
|
1301
|
-
delete sources[existingId];
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
mergedVariables[varId] = variable;
|
|
1305
|
-
sources[varId] = result.sources[varId] ?? sources[varId];
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
const parentEnv = {};
|
|
1309
|
-
const parentSecretKeys = /* @__PURE__ */ new Set();
|
|
1310
|
-
const parentKeySources = {};
|
|
1311
|
-
for (const [varId, variable] of Object.entries(mergedVariables)) {
|
|
1312
|
-
const slot = variable.value;
|
|
1313
|
-
parentEnv[variable.key] = slot.pre + slot.value + slot.post;
|
|
1314
|
-
if (slot.isSecret) {
|
|
1315
|
-
parentSecretKeys.add(variable.key);
|
|
1316
|
-
}
|
|
1317
|
-
const source = sources[varId];
|
|
1318
|
-
if (source) {
|
|
1319
|
-
parentKeySources[variable.key] = { environmentId: source.environmentId, environmentName: source.environmentName };
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
const isInterpRef = (v) => /\{\{environment\.[^}]+\}\}/.test(v);
|
|
1323
|
-
for (const result of parentResults) {
|
|
1324
|
-
for (const [key, value] of Object.entries(result.parentEnv)) {
|
|
1325
|
-
const existingIsRef = key in parentEnv && isInterpRef(parentEnv[key]);
|
|
1326
|
-
const newIsConcrete = !isInterpRef(value);
|
|
1327
|
-
if (!(key in parentEnv) || existingIsRef && newIsConcrete) {
|
|
1328
|
-
parentEnv[key] = value;
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
for (const key of result.parentSecretKeys) {
|
|
1332
|
-
parentSecretKeys.add(key);
|
|
1333
|
-
}
|
|
1334
|
-
for (const [key, source] of Object.entries(result.parentKeySources)) {
|
|
1335
|
-
if (!(key in parentKeySources)) {
|
|
1336
|
-
parentKeySources[key] = source;
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
for (const key of this.collectSecretKeys(mergedVariables, parentSecretKeys)) {
|
|
1341
|
-
parentSecretKeys.add(key);
|
|
1342
|
-
}
|
|
1343
|
-
const ownVariables = project ? project(environment.variables) : environment.variables;
|
|
1344
|
-
for (const [varId, variable] of Object.entries(ownVariables)) {
|
|
1345
|
-
let wasInherited = varId in mergedVariables;
|
|
1346
|
-
for (const [existingId, existingVar] of Object.entries(mergedVariables)) {
|
|
1347
|
-
if (existingVar.key === variable.key && existingId !== varId) {
|
|
1348
|
-
delete mergedVariables[existingId];
|
|
1349
|
-
delete sources[existingId];
|
|
1350
|
-
wasInherited = true;
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
mergedVariables[varId] = variable;
|
|
1354
|
-
sources[varId] = {
|
|
1355
|
-
environmentId: environment.id,
|
|
1356
|
-
environmentName: environment.name,
|
|
1357
|
-
overridden: wasInherited
|
|
1358
|
-
};
|
|
1359
|
-
}
|
|
1360
|
-
return {
|
|
1361
|
-
variables: mergedVariables,
|
|
1362
|
-
sources,
|
|
1363
|
-
parentEnv,
|
|
1364
|
-
parentSecretKeys,
|
|
1365
|
-
parentKeySources
|
|
1366
|
-
};
|
|
1367
|
-
}
|
|
1368
|
-
};
|
|
1369
|
-
var environmentResolver = new EnvironmentResolver();
|
|
1370
|
-
|
|
1371
|
-
// ../../packages/ai-capabilities/src/capabilities.ts
|
|
1372
|
-
import { z as z3 } from "zod";
|
|
1373
|
-
function toList(raw) {
|
|
1374
|
-
if (!raw) return [];
|
|
1375
|
-
const text = raw.trim();
|
|
1376
|
-
if (text.startsWith("[")) {
|
|
1377
|
-
try {
|
|
1378
|
-
const parsed = JSON.parse(text);
|
|
1379
|
-
if (Array.isArray(parsed)) return parsed.map((v) => String(v).trim()).filter(Boolean);
|
|
1380
|
-
} catch {
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
return text.split(",").map((v) => v.trim().replace(/^["'[]+|["'\]]+$/g, "")).filter(Boolean);
|
|
1384
|
-
}
|
|
1385
|
-
function clearable(raw, parse, key) {
|
|
1386
|
-
if (raw === void 0) return {};
|
|
1387
|
-
const text = String(raw).trim();
|
|
1388
|
-
if (!text || text === "null") return { [key]: null };
|
|
1389
|
-
return { [key]: parse(text) };
|
|
1390
|
-
}
|
|
1391
|
-
function gitTarget(p) {
|
|
1392
|
-
return {
|
|
1393
|
-
...p.serviceId ? { serviceId: p.serviceId } : {},
|
|
1394
|
-
...p.projectId ? { projectId: p.projectId } : {},
|
|
1395
|
-
...p.agentId ? { agentId: p.agentId } : {},
|
|
1396
|
-
...p.orgId ? { orgId: p.orgId } : {}
|
|
1397
|
-
};
|
|
1398
|
-
}
|
|
1399
|
-
var toBool = (raw) => raw.trim().toLowerCase() === "true";
|
|
1400
|
-
var newId = () => globalThis.crypto.randomUUID();
|
|
1401
|
-
function listItem(raw) {
|
|
1402
|
-
try {
|
|
1403
|
-
return JSON.parse(raw);
|
|
1404
|
-
} catch {
|
|
1405
|
-
return raw;
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
function parseTyped(name, schema, raw) {
|
|
1409
|
-
if (raw === void 0) return void 0;
|
|
1410
|
-
let result = raw.trim() === "null" ? schema.safeParse(null) : schema.safeParse(raw);
|
|
1411
|
-
if (!result.success && raw.trim() === "null") result = schema.safeParse(raw);
|
|
1412
|
-
if (!result.success) {
|
|
1413
|
-
const text = raw.trim();
|
|
1414
|
-
const json = listItem(text);
|
|
1415
|
-
if (json !== text) result = schema.safeParse(json);
|
|
1416
|
-
if (!result.success && !/^[[{]/.test(text)) {
|
|
1417
|
-
const list = schema.safeParse(toList(text).map(listItem));
|
|
1418
|
-
if (list.success) result = list;
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
if (!result.success) throw new Error(`${name}: ${z3.prettifyError(result.error)}`);
|
|
1422
|
-
return result.data;
|
|
1423
|
-
}
|
|
1424
|
-
function withIds(items) {
|
|
1425
|
-
return items.map((item) => ({ ...item, id: item.id || newId() }));
|
|
1426
|
-
}
|
|
1427
|
-
var optionalId = (schema) => schema.extend({ id: z3.string().min(1).max(64).optional() });
|
|
1428
|
-
var DockerConfigInput = DockerConfigSchema.extend({
|
|
1429
|
-
bootstrapCommands: z3.array(optionalId(BootstrapCommandSchema)).max(20).default([])
|
|
1430
|
-
}).refine((c) => !!c.image !== !!c.dockerfile, {
|
|
1431
|
-
message: 'dockerConfig needs exactly one of "image" (an image to pull) or "dockerfile" (a Dockerfile to build)'
|
|
1432
|
-
});
|
|
1433
|
-
var SERVICE_FIELDS = [
|
|
1434
|
-
{ name: "description", description: "What the service is, in a sentence" },
|
|
1435
|
-
{ name: "agentId", description: "The agent that runs it, from list_agents. Omit to inherit from the environment.", clearable: true },
|
|
1436
|
-
{ name: "cwd", description: `Working directory, RELATIVE to the project root (e.g. "apps/web", or omit for the root). The project is synced between machines and lives in a different folder on each: Runeya resolves a relative cwd against each machine's own project folder, while an absolute path (/home/you/\u2026) breaks on every other machine. Use an absolute path only for something outside the project, written with ~ or an environment variable ($NAME) rather than a literal home directory.`, clearable: true },
|
|
1437
|
-
{ name: "command", description: 'Shortcut for a single command line (e.g. "npm run dev"). Replaces every existing command. Use commands for several.' },
|
|
1438
|
-
{ name: "commands", description: 'The processes of the service. They all start AT THE SAME TIME, in parallel, and the service runs while they run \u2014 not one after another. Use several for processes that live side by side (a web server and a worker). Steps that must follow each other (build, then install, then launch) go in ONE command chained with &&. A one-off action the user triggers by hand (build, deploy, read logs) is a shortcut, not a command. Each command runs in the service cwd, or its own "cwd", relative to the project root. No absolute paths of this machine: the service is synced to other machines. Replaces every existing command. "id" is generated when omitted; "shell" runs the line through a shell (default true).', schema: z3.array(optionalId(ServiceCommandSchema)).max(50) },
|
|
1439
|
-
{ name: "ports", description: "Ports the service listens on. Declare them: they become {{service.PORT}}, {{service.URL}} and {{services.<slug>.URL}} for the other services, which environment variables should reference instead of repeating the numbers.", schema: ServiceConfigSchema.shape.ports.unwrap().unwrap() },
|
|
1440
|
-
{ name: "groups", description: "Groups the service belongs to. Replaces the current ones.", schema: ServiceConfigSchema.shape.groups.unwrap().unwrap() },
|
|
1441
|
-
{ name: "url", description: "URL where the service is reached" },
|
|
1442
|
-
{ name: "localUrls", description: 'Local domain names routed to a port of the service (e.g. {"url":"api.localhost.dev","port":3000})', schema: ServiceConfigSchema.shape.localUrls.unwrap().unwrap() },
|
|
1443
|
-
{ name: "openapiUrl", description: "URL of its OpenAPI document" },
|
|
1444
|
-
{ name: "docsPath", description: "Where its markdown docs live, relative to cwd or absolute. Defaults to cwd." },
|
|
1445
|
-
{ name: "git", description: 'Its git repository: "remote" URL and "home" page', schema: ServiceConfigSchema.shape.git.unwrap(), clearable: true },
|
|
1446
|
-
{ name: "meta", description: "Free key/value metadata (strings)", schema: ServiceConfigSchema.shape.meta.unwrap(), clearable: true },
|
|
1447
|
-
{ name: "runner", description: '"native" runs the commands on the machine; "docker" runs the service in a container and then needs dockerConfig', schema: ServiceConfigSchema.shape.runner.unwrap().unwrap() },
|
|
1448
|
-
{ name: "dockerConfig", description: 'With runner "docker": the container. Exactly one of "image" (pull) or "dockerfile" (inline Dockerfile, built with "buildContext", "buildArgs", "targetStage"). Ports and volumes use Docker syntax ("8080:80", "./data:/data"); host paths in volumes relative to the service directory, never an absolute path of this machine. "useNativeCommand" runs the service commands inside the container instead of its CMD ("nativeCommandMode": one shared container or one per command). "bootstrapCommands" run once when the container starts. Replaces the whole Docker configuration: read it first with get_service to keep what you do not change.', schema: DockerConfigInput },
|
|
1449
|
-
{ name: "healthCheck", description: 'How to tell the service is ready: an HTTP "path" to call, or a "command" that must succeed', schema: HealthCheckSchema, clearable: true },
|
|
1450
|
-
{ name: "autoRestart", description: "Restart the service when it crashes", schema: z3.boolean() },
|
|
1451
|
-
{ name: "restartStrategy", description: "When to restart it", schema: RestartStrategySchema.unwrap() },
|
|
1452
|
-
{ name: "maxRestarts", description: "Give up after this many restarts", schema: ServiceConfigSchema.shape.maxRestarts.unwrap() },
|
|
1453
|
-
{ name: "restartBackoffMs", description: "Delay before a restart, in milliseconds", schema: ServiceConfigSchema.shape.restartBackoffMs.unwrap().unwrap() },
|
|
1454
|
-
{ name: "clearLogsOnStart", description: "Clear the logs each time the service starts", schema: z3.boolean() },
|
|
1455
|
-
{ name: "shortcuts", description: 'One-click actions shown on the service, run on demand and independently of the running commands (build, deploy, migrate, read logs). Same path rules as commands: relative to the project root, never an absolute path of this machine. Replaces the current ones. "id" is generated when omitted.', schema: z3.array(optionalId(ServiceShortcutSchema)).max(50) },
|
|
1456
|
-
{ name: "parserIds", description: "Log parsers applied to its output, by id", schema: ServiceConfigSchema.shape.parserIds.unwrap() },
|
|
1457
|
-
{ name: "logSources", description: 'Extra log sources beyond the console output (files, databases\u2026). Replaces the current ones. Credentials (mongo connectionString, mysql password) read as "***": send "***" back to keep the stored value, or the real value to change it.', schema: ServiceConfigSchema.shape.logSources.unwrap().unwrap() }
|
|
1458
|
-
];
|
|
1459
|
-
function serviceParams(clearable2) {
|
|
1460
|
-
return SERVICE_FIELDS.map((f) => {
|
|
1461
|
-
const nullable = clearable2 && f.clearable;
|
|
1462
|
-
const base = f.schema ?? (nullable ? z3.string() : void 0);
|
|
1463
|
-
return {
|
|
1464
|
-
name: f.name,
|
|
1465
|
-
description: nullable ? `${f.description} null clears it.` : f.description,
|
|
1466
|
-
required: false,
|
|
1467
|
-
...base ? { schema: nullable ? base.nullable() : base } : {}
|
|
1468
|
-
};
|
|
1469
|
-
});
|
|
1470
|
-
}
|
|
1471
|
-
function serviceFields(p, params) {
|
|
1472
|
-
const out = {};
|
|
1473
|
-
for (const param of params) {
|
|
1474
|
-
if (p[param.name] === void 0 || param.name === "command") continue;
|
|
1475
|
-
out[param.name] = param.schema ? parseTyped(param.name, param.schema, p[param.name]) : p[param.name];
|
|
1476
|
-
}
|
|
1477
|
-
if (p.command !== void 0 && p.commands === void 0) out["commands"] = [{ label: "", command: p.command, shell: true }];
|
|
1478
|
-
if (Array.isArray(out["commands"])) out["commands"] = withIds(out["commands"]);
|
|
1479
|
-
if (Array.isArray(out["shortcuts"])) out["shortcuts"] = withIds(out["shortcuts"]);
|
|
1480
|
-
const docker = out["dockerConfig"];
|
|
1481
|
-
if (docker?.bootstrapCommands) out["dockerConfig"] = { ...docker, bootstrapCommands: withIds(docker.bootstrapCommands) };
|
|
1482
|
-
return out;
|
|
1483
|
-
}
|
|
1484
|
-
function maskServices(result) {
|
|
1485
|
-
if (Array.isArray(result)) return result.map(maskServices);
|
|
1486
|
-
if (result && typeof result === "object" && Array.isArray(result.logSources)) {
|
|
1487
|
-
const service = result;
|
|
1488
|
-
return { ...service, logSources: maskLogSourceSecrets(service.logSources) };
|
|
1489
|
-
}
|
|
1490
|
-
return result;
|
|
1491
|
-
}
|
|
1492
|
-
var refs = (scope) => BUILTIN_VARIABLES.filter((b) => b.scope === scope).map((b) => `{{${b.name}}}`).join(", ");
|
|
1493
|
-
var BUILTINS_HELP = `Built-in references Runeya fills itself \u2014 filled when the service is deployed, from the service that receives the variable: ${refs("server")}, the numbered {{service.PORT_2}} / {{service.URL_2}}\u2026 for its other ports, and {{services.<slug>.PORT}}, .URL, .BASE_URL, .NAME, .ID for a sibling service of the project (slug = its name in lowercase-dashes). Filled on the machine when the service starts: ${refs("agent")}.`;
|
|
1494
|
-
var CREATE_SERVICE_PARAMS = serviceParams(false);
|
|
1495
|
-
var UPDATE_SERVICE_PARAMS = serviceParams(true);
|
|
1496
|
-
var RUNEYA_CAPABILITIES = [
|
|
1497
|
-
{
|
|
1498
|
-
name: "list_services",
|
|
1499
|
-
transformOutput: maskServices,
|
|
1500
|
-
description: "List services, with the serviceId and agentId to use in other calls. Pass the projectId your context names to stay on the project the user is looking at, or another one from list_projects to look elsewhere. Called with no project, this can answer for every project of every organization on the machine \u2014 far more than the interface shows.",
|
|
1501
|
-
route: "service.list",
|
|
1502
|
-
method: "GET",
|
|
1503
|
-
params: [
|
|
1504
|
-
{ name: "projectId", description: "Restrict to this project, from list_projects. Omit to list every service on the machine.", required: false },
|
|
1505
|
-
{ name: "orgId", description: "Its organization. Needed only when the same project id exists in several.", required: false }
|
|
1506
|
-
],
|
|
1507
|
-
buildInput: (p) => p.projectId ? { projectId: p.projectId, ...p.orgId ? { orgId: p.orgId } : {} } : void 0
|
|
1508
|
-
},
|
|
1509
|
-
{
|
|
1510
|
-
name: "get_service_status",
|
|
1511
|
-
description: "Get the current status (running, stopped, etc.) of a service. A service that has never been started on this agent has no process yet, and this answers 404 Process not found \u2014 that means STOPPED, not forbidden and not missing. Start it, or read its configuration with get_service.",
|
|
1512
|
-
route: "process.get",
|
|
1513
|
-
method: "GET",
|
|
1514
|
-
params: [
|
|
1515
|
-
{ name: "serviceId", description: "The service ID", required: true },
|
|
1516
|
-
{ name: "agentId", description: "The agent ID hosting this service", required: true }
|
|
1517
|
-
],
|
|
1518
|
-
buildInput: (p) => ({ agentId: p.agentId, id: p.serviceId })
|
|
1519
|
-
},
|
|
1520
|
-
{
|
|
1521
|
-
name: "get_logs",
|
|
1522
|
-
description: "Get the recent buffered logs for a service.",
|
|
1523
|
-
route: "process.logs",
|
|
1524
|
-
method: "GET",
|
|
1525
|
-
params: [
|
|
1526
|
-
{ name: "serviceId", description: "The service ID", required: true },
|
|
1527
|
-
{ name: "agentId", description: "The agent ID hosting this service", required: true }
|
|
1528
|
-
],
|
|
1529
|
-
buildInput: (p) => ({ agentId: p.agentId, processId: p.serviceId })
|
|
1530
|
-
},
|
|
1531
|
-
{
|
|
1532
|
-
name: "start_service",
|
|
1533
|
-
description: "Start a service on an agent.",
|
|
1534
|
-
route: "process.start",
|
|
1535
|
-
method: "POST",
|
|
1536
|
-
params: [
|
|
1537
|
-
{ name: "serviceId", description: "The service ID to start", required: true },
|
|
1538
|
-
{ name: "agentId", description: "The agent ID hosting this service", required: true }
|
|
1539
|
-
],
|
|
1540
|
-
buildInput: (p) => ({ agentId: p.agentId, processId: p.serviceId }),
|
|
1541
|
-
destructive: true
|
|
1542
|
-
},
|
|
1543
|
-
{
|
|
1544
|
-
name: "stop_service",
|
|
1545
|
-
description: "Stop a running service on an agent.",
|
|
1546
|
-
route: "process.stop",
|
|
1547
|
-
method: "POST",
|
|
1548
|
-
params: [
|
|
1549
|
-
{ name: "serviceId", description: "The service ID to stop", required: true },
|
|
1550
|
-
{ name: "agentId", description: "The agent ID hosting this service", required: true }
|
|
1551
|
-
],
|
|
1552
|
-
buildInput: (p) => ({ agentId: p.agentId, processId: p.serviceId }),
|
|
1553
|
-
destructive: true
|
|
1554
|
-
},
|
|
1555
|
-
{
|
|
1556
|
-
name: "restart_service",
|
|
1557
|
-
description: "Restart a service on an agent (stop \u2192 redeploy \u2192 start).",
|
|
1558
|
-
route: "process.restart",
|
|
1559
|
-
method: "POST",
|
|
1560
|
-
params: [
|
|
1561
|
-
{ name: "serviceId", description: "The service ID to restart", required: true },
|
|
1562
|
-
{ name: "agentId", description: "The agent ID hosting this service", required: true }
|
|
1563
|
-
],
|
|
1564
|
-
buildInput: (p) => ({ agentId: p.agentId, processId: p.serviceId }),
|
|
1565
|
-
destructive: true
|
|
1566
|
-
},
|
|
1567
|
-
// ─── Utility ─────────────────────────────────────────────────────────────
|
|
1568
|
-
{
|
|
1569
|
-
name: "wait",
|
|
1570
|
-
description: "Wait for a given number of milliseconds before continuing.",
|
|
1571
|
-
route: "utility.wait",
|
|
1572
|
-
method: "POST",
|
|
1573
|
-
params: [
|
|
1574
|
-
{ name: "ms", description: "Number of MILLISECONDS to wait. Convert from seconds: 1s=1000, 10s=10000, 1min=60000. Max 300000.", required: true }
|
|
1575
|
-
],
|
|
1576
|
-
buildInput: (p) => ({ ms: Number(p.ms) }),
|
|
1577
|
-
usageHint: '"wait 5s" \u2192 wait(ms=5000), "pause 10 seconds" \u2192 wait(ms=10000), "attends 3 secondes" \u2192 wait(ms=3000). Always convert to milliseconds.'
|
|
1578
|
-
},
|
|
1579
|
-
// ─── Kanban ───────────────────────────────────────────────────────────────
|
|
1580
|
-
{
|
|
1581
|
-
name: "kanban_list_boards",
|
|
1582
|
-
description: "List the kanban boards of the organization. Returns the organizationId actually queried, the boardId to use in the other kanban tools, plus column and card counts. Cards themselves are not included \u2014 use kanban_get_board for those. An empty list means that organization really has no board: a failure to reach the cloud is reported as an error instead, never as an empty list.",
|
|
1583
|
-
route: "kanban.listBoards",
|
|
1584
|
-
method: "GET",
|
|
1585
|
-
params: [
|
|
1586
|
-
{ name: "includeArchived", description: 'Set to "true" to include archived boards. Default: false.', required: false },
|
|
1587
|
-
{ name: "organizationId", description: "Which organization to read. Optional: needed only when the account belongs to several and the error message asks for it.", required: false }
|
|
1588
|
-
],
|
|
1589
|
-
buildInput: (p) => ({
|
|
1590
|
-
includeArchived: p.includeArchived === "true",
|
|
1591
|
-
...p.organizationId ? { organizationId: p.organizationId } : {}
|
|
1592
|
-
})
|
|
1593
|
-
},
|
|
1594
|
-
{
|
|
1595
|
-
name: "kanban_get_board",
|
|
1596
|
-
description: "Read one kanban board: its columns, and the cards in each (id, title, markdown body, assignees, comment count, and attachment metadata \u2014 use kanban_download_attachment to fetch the bytes of a file). Use the exact column names it returns when moving or adding a card.",
|
|
1597
|
-
route: "kanban.getBoard",
|
|
1598
|
-
method: "GET",
|
|
1599
|
-
params: [
|
|
1600
|
-
{ name: "boardId", description: "The kanban board ID, from kanban_list_boards", required: true }
|
|
1601
|
-
],
|
|
1602
|
-
buildInput: (p) => ({ boardId: p.boardId })
|
|
1603
|
-
},
|
|
1604
|
-
{
|
|
1605
|
-
name: "kanban_get_card_comments",
|
|
1606
|
-
description: "Read the comment thread of one kanban card, newest last: who wrote each comment (ai or user, with the person's name), when, its text, and its attachments. The comments in your task context are a snapshot taken when the task started \u2014 call this to see anything written since, whenever someone tells you a comment was added or kanban_get_board reports a higher commentCount than you have.",
|
|
1607
|
-
route: "kanban.getCardComments",
|
|
1608
|
-
method: "GET",
|
|
1609
|
-
params: [
|
|
1610
|
-
{ name: "boardId", description: "The kanban board ID", required: true },
|
|
1611
|
-
{ name: "cardId", description: "The card ID whose comments to read", required: true },
|
|
1612
|
-
{ name: "limit", description: "How many of the most recent comments to return (1-200). Default: 50.", required: false }
|
|
1613
|
-
],
|
|
1614
|
-
buildInput: (p) => ({
|
|
1615
|
-
boardId: p.boardId,
|
|
1616
|
-
cardId: p.cardId,
|
|
1617
|
-
...p.limit ? { limit: Number(p.limit) } : {}
|
|
1618
|
-
}),
|
|
1619
|
-
usageHint: '"le support a ajout\xE9 un commentaire" / "regarde les nouveaux commentaires" \u2192 kanban_get_card_comments(boardId, cardId). Never answer from the snapshot in your context when fresh comments are mentioned.'
|
|
1620
|
-
},
|
|
1621
|
-
{
|
|
1622
|
-
name: "kanban_search_cards",
|
|
1623
|
-
description: 'Search kanban cards by free text, matched against their title, body, comments and the support conversations they follow (title, messages, internal notes, and the names of message authors) \u2014 accent- and case-insensitive. Archived cards are included. Searches every board of the organization unless boardId narrows it down. createdBy narrows to the cards someone created ("les cartes cr\xE9\xE9es par Isabelle"), alone or with a query.',
|
|
1624
|
-
route: "kanban.searchCards",
|
|
1625
|
-
method: "GET",
|
|
1626
|
-
params: [
|
|
1627
|
-
{ name: "query", description: "Text to look for in card titles, bodies, comments and followed conversations. Optional when createdBy is given.", required: false },
|
|
1628
|
-
{ name: "createdBy", description: "Only cards created by this author: a user id, or a name (accent- and case-insensitive, partial match).", required: false },
|
|
1629
|
-
{ name: "boardId", description: "Restrict the search to this board. Omit to search all boards.", required: false },
|
|
1630
|
-
{ name: "limit", description: "Maximum number of cards to return (1-100). Default: 25.", required: false },
|
|
1631
|
-
{ name: "organizationId", description: "Which organization to search. Optional: needed only when the account belongs to several and the error message asks for it.", required: false }
|
|
1632
|
-
],
|
|
1633
|
-
buildInput: (p) => ({
|
|
1634
|
-
...p.query ? { query: p.query } : {},
|
|
1635
|
-
...p.createdBy ? { createdBy: p.createdBy } : {},
|
|
1636
|
-
...p.boardId ? { boardId: p.boardId } : {},
|
|
1637
|
-
...p.limit ? { limit: Number(p.limit) } : {},
|
|
1638
|
-
...p.organizationId ? { organizationId: p.organizationId } : {}
|
|
1639
|
-
})
|
|
1640
|
-
},
|
|
1641
|
-
{
|
|
1642
|
-
name: "kanban_download_attachment",
|
|
1643
|
-
description: "Download a kanban card attachment (image, PDF, any file) onto this machine and return its local path, so it can be opened with file-reading tools. Attachment ids come from the card context, from kanban_get_board, or from kanban_get_card_comments \u2014 a file attached to a comment downloads exactly like one attached to the card.",
|
|
1644
|
-
route: "kanban.downloadAttachment",
|
|
1645
|
-
method: "POST",
|
|
1646
|
-
params: [
|
|
1647
|
-
{ name: "attachmentId", description: "The attachment ID, as listed on the card", required: true },
|
|
1648
|
-
{ name: "boardId", description: "The board the attachment comes from. Pass it whenever you know it: on a machine linked to several cloud accounts, it is what says which one holds the file.", required: false },
|
|
1649
|
-
{ name: "organizationId", description: "Which organization the attachment belongs to. Optional: needed only when neither the board id is known nor the machine has a single account.", required: false }
|
|
1650
|
-
],
|
|
1651
|
-
buildInput: (p) => ({
|
|
1652
|
-
attachmentId: p.attachmentId,
|
|
1653
|
-
...p.boardId ? { boardId: p.boardId } : {},
|
|
1654
|
-
...p.organizationId ? { orgId: p.organizationId } : {}
|
|
1655
|
-
})
|
|
1656
|
-
},
|
|
1657
|
-
{
|
|
1658
|
-
name: "kanban_move_card",
|
|
1659
|
-
description: "Move a kanban card to another column. Use the exact column name.",
|
|
1660
|
-
route: "kanban.moveCard",
|
|
1661
|
-
method: "POST",
|
|
1662
|
-
params: [
|
|
1663
|
-
{ name: "boardId", description: "The kanban board ID", required: true },
|
|
1664
|
-
{ name: "cardId", description: "The card ID to move", required: true },
|
|
1665
|
-
{ name: "targetColumn", description: "Exact name of the target column", required: true }
|
|
1666
|
-
],
|
|
1667
|
-
buildInput: (p) => ({ boardId: p.boardId, cardId: p.cardId, targetColumn: p.targetColumn })
|
|
1668
|
-
},
|
|
1669
|
-
{
|
|
1670
|
-
name: "kanban_add_comment",
|
|
1671
|
-
description: "Add a comment to a kanban card.",
|
|
1672
|
-
route: "kanban.addComment",
|
|
1673
|
-
method: "POST",
|
|
1674
|
-
params: [
|
|
1675
|
-
{ name: "boardId", description: "The kanban board ID", required: true },
|
|
1676
|
-
{ name: "cardId", description: "The card ID", required: true },
|
|
1677
|
-
{ name: "comment", description: "The comment text (markdown supported)", required: true }
|
|
1678
|
-
],
|
|
1679
|
-
buildInput: (p) => ({ boardId: p.boardId, cardId: p.cardId, comment: p.comment })
|
|
1680
|
-
},
|
|
1681
|
-
{
|
|
1682
|
-
name: "kanban_get_card_conversations",
|
|
1683
|
-
description: "Read the customer support conversations (Intercom\u2026) linked to one kanban card: for each, its title, provider, state (open/resolved), link, the opening message and the thread of messages (author name, customer or team, internal note or not, date, text, attached files \u2014 fetch their bytes with kanban_download_attachment), plus the other cards following the same conversation. These messages are NOT comments of the card \u2014 read them here.",
|
|
1684
|
-
route: "kanban.getCardConversations",
|
|
1685
|
-
method: "GET",
|
|
1686
|
-
params: [
|
|
1687
|
-
{ name: "boardId", description: "The kanban board ID", required: true },
|
|
1688
|
-
{ name: "cardId", description: "The card ID whose conversations to read", required: true },
|
|
1689
|
-
{ name: "limit", description: "How many of the most recent messages to return per conversation (1-500). Default: 100.", required: false }
|
|
1690
|
-
],
|
|
1691
|
-
buildInput: (p) => ({
|
|
1692
|
-
boardId: p.boardId,
|
|
1693
|
-
cardId: p.cardId,
|
|
1694
|
-
...p.limit ? { limit: Number(p.limit) } : {}
|
|
1695
|
-
}),
|
|
1696
|
-
usageHint: `"regarde la conversation Intercom de la carte" / "qu'a dit le client" \u2192 kanban_get_card_conversations(boardId, cardId).`
|
|
1697
|
-
},
|
|
1698
|
-
{
|
|
1699
|
-
name: "kanban_search_conversations",
|
|
1700
|
-
description: "Search customer support conversations (Intercom\u2026) at the providers connected to the board's organization, by words of the conversation title, or by conversation number or URL. Read-only at the provider. Each result gives connectionId and externalId (needed to link it), title, state, a preview and the cards already following it.",
|
|
1701
|
-
route: "kanban.searchConversations",
|
|
1702
|
-
method: "GET",
|
|
1703
|
-
params: [
|
|
1704
|
-
{ name: "boardId", description: "Any kanban board ID of the organization to search in", required: true },
|
|
1705
|
-
{ name: "query", description: "Words of the conversation title, a conversation number, or a conversation URL", required: true }
|
|
1706
|
-
],
|
|
1707
|
-
buildInput: (p) => ({ boardId: p.boardId, query: p.query })
|
|
1708
|
-
},
|
|
1709
|
-
{
|
|
1710
|
-
name: "kanban_link_conversation",
|
|
1711
|
-
description: "Link a customer support conversation found with kanban_search_conversations to a kanban card. Its whole thread is imported and new messages keep arriving on the card. A conversation can be linked to several cards. Nothing is written at the provider.",
|
|
1712
|
-
route: "kanban.linkConversation",
|
|
1713
|
-
method: "POST",
|
|
1714
|
-
params: [
|
|
1715
|
-
{ name: "boardId", description: "The kanban board ID of the card", required: true },
|
|
1716
|
-
{ name: "cardId", description: "The card to link the conversation to", required: true },
|
|
1717
|
-
{ name: "connectionId", description: "The connectionId of the search result", required: true },
|
|
1718
|
-
{ name: "externalId", description: "The externalId of the search result", required: true }
|
|
1719
|
-
],
|
|
1720
|
-
buildInput: (p) => ({ boardId: p.boardId, cardId: p.cardId, connectionId: p.connectionId, externalId: p.externalId })
|
|
1721
|
-
},
|
|
1722
|
-
{
|
|
1723
|
-
name: "kanban_get_card_links",
|
|
1724
|
-
description: "Read the filiation of a board: which card depends on which. Returns one entry per link (child card \u2194 parent card), plus the title, board, column, assignees and comment count of any linked card living on another board. Use it when a card context says it has a parent or sub-cards and you need their ids to read them.",
|
|
1725
|
-
route: "kanban.getCardLinks",
|
|
1726
|
-
method: "GET",
|
|
1727
|
-
params: [
|
|
1728
|
-
{ name: "boardId", description: "The kanban board ID whose links to read", required: true }
|
|
1729
|
-
],
|
|
1730
|
-
buildInput: (p) => ({ boardId: p.boardId })
|
|
1731
|
-
},
|
|
1732
|
-
{
|
|
1733
|
-
name: "kanban_set_card_parent",
|
|
1734
|
-
description: "Link a kanban card to a parent card, or detach it. The parent is the card the other one depends on \u2014 an epic and its tasks, a bug and its follow-ups. A card has at most one parent, and setting a new one replaces it. Omit parentCardId to detach. kanban_get_board reports the current filiation as parentCardId / childCardIds on each card.",
|
|
1735
|
-
route: "kanban.setCardParent",
|
|
1736
|
-
method: "POST",
|
|
1737
|
-
params: [
|
|
1738
|
-
{ name: "boardId", description: "The kanban board ID of the card being linked", required: true },
|
|
1739
|
-
{ name: "cardId", description: "The card that depends on the parent (the child)", required: true },
|
|
1740
|
-
{ name: "parentCardId", description: "The parent card ID. Omit to detach the card from its parent.", required: false },
|
|
1741
|
-
{ name: "parentBoardId", description: "The parent's board, when it lives on another board of the same organization. Defaults to boardId.", required: false }
|
|
1742
|
-
],
|
|
1743
|
-
buildInput: (p) => ({
|
|
1744
|
-
boardId: p.boardId,
|
|
1745
|
-
cardId: p.cardId,
|
|
1746
|
-
...p.parentCardId ? { parentCardId: p.parentCardId } : {},
|
|
1747
|
-
...p.parentBoardId ? { parentBoardId: p.parentBoardId } : {}
|
|
1748
|
-
})
|
|
1749
|
-
},
|
|
1750
|
-
{
|
|
1751
|
-
name: "kanban_place_card",
|
|
1752
|
-
description: "Place an existing kanban card on another board of the same organization, without copying it \u2014 a reference. The card stays on its origin board; the target board gives it a column of its own. Editing, commenting, archiving or deleting the card from either board acts on the same card; moving it on the target board only moves the reference. Use it to show an issue from an issue list on a team kanban, for instance.",
|
|
1753
|
-
route: "kanban.placeCard",
|
|
1754
|
-
method: "POST",
|
|
1755
|
-
params: [
|
|
1756
|
-
{ name: "boardId", description: "The board to place the card on (not its own board)", required: true },
|
|
1757
|
-
{ name: "cardId", description: "The card to place", required: true },
|
|
1758
|
-
{ name: "column", description: "The column on that board: its ID or its title", required: true }
|
|
1759
|
-
],
|
|
1760
|
-
buildInput: (p) => ({ boardId: p.boardId, cardId: p.cardId, column: p.column })
|
|
1761
|
-
},
|
|
1762
|
-
{
|
|
1763
|
-
name: "kanban_remove_card_placement",
|
|
1764
|
-
description: "Remove a card from a board where it was placed as a reference. The card itself is untouched and stays on its origin board.",
|
|
1765
|
-
route: "kanban.unplaceCard",
|
|
1766
|
-
method: "POST",
|
|
1767
|
-
params: [
|
|
1768
|
-
{ name: "boardId", description: "The board the reference is on", required: true },
|
|
1769
|
-
{ name: "cardId", description: "The card to remove from that board", required: true }
|
|
1770
|
-
],
|
|
1771
|
-
buildInput: (p) => ({ boardId: p.boardId, cardId: p.cardId })
|
|
1772
|
-
},
|
|
1773
|
-
{
|
|
1774
|
-
name: "kanban_get_card_placements",
|
|
1775
|
-
description: "List where a kanban card is: its origin board (home: true), then every board it is placed on as a reference, with the column on each. kanban_get_board flags a referenced card with an `origin` field naming its origin board.",
|
|
1776
|
-
route: "kanban.getCardPlacements",
|
|
1777
|
-
method: "GET",
|
|
1778
|
-
params: [
|
|
1779
|
-
{ name: "boardId", description: "Any board the card is on", required: true },
|
|
1780
|
-
{ name: "cardId", description: "The card", required: true }
|
|
1781
|
-
],
|
|
1782
|
-
buildInput: (p) => ({ boardId: p.boardId, cardId: p.cardId })
|
|
1783
|
-
},
|
|
1784
|
-
{
|
|
1785
|
-
name: "kanban_add_card",
|
|
1786
|
-
description: "Add a new card to a kanban column.",
|
|
1787
|
-
route: "kanban.addCard",
|
|
1788
|
-
method: "POST",
|
|
1789
|
-
params: [
|
|
1790
|
-
{ name: "boardId", description: "The kanban board ID", required: true },
|
|
1791
|
-
{ name: "column", description: "Exact name of the target column", required: true },
|
|
1792
|
-
{ name: "title", description: "Card title", required: true },
|
|
1793
|
-
{ name: "content", description: "Card content in markdown", required: false }
|
|
1794
|
-
],
|
|
1795
|
-
buildInput: (p) => ({ boardId: p.boardId, column: p.column, title: p.title, content: p.content })
|
|
1796
|
-
},
|
|
1797
|
-
{
|
|
1798
|
-
name: "kanban_get_card",
|
|
1799
|
-
description: "Read one kanban card by id, including archived ones. Cheaper than fetching the whole board, and the only way to read a card that has been archived.",
|
|
1800
|
-
route: "kanban.getCard",
|
|
1801
|
-
method: "GET",
|
|
1802
|
-
params: [
|
|
1803
|
-
{ name: "boardId", description: "The kanban board ID", required: true },
|
|
1804
|
-
{ name: "cardId", description: "The card ID", required: true }
|
|
1805
|
-
],
|
|
1806
|
-
buildInput: (p) => ({ boardId: p.boardId, cardId: p.cardId })
|
|
1807
|
-
},
|
|
1808
|
-
{
|
|
1809
|
-
name: "kanban_update_card",
|
|
1810
|
-
description: "Update an existing kanban card. Only the fields you pass are changed; passing null to priority, color or deadline clears it. Prefer this over creating a second card when something needs correcting.",
|
|
1811
|
-
route: "kanban.updateCard",
|
|
1812
|
-
method: "POST",
|
|
1813
|
-
params: [
|
|
1814
|
-
{ name: "boardId", description: "The kanban board ID", required: true },
|
|
1815
|
-
{ name: "cardId", description: "The card ID", required: true },
|
|
1816
|
-
{ name: "title", description: "New title", required: false },
|
|
1817
|
-
{ name: "content", description: "New card content, in markdown", required: false },
|
|
1818
|
-
{ name: "priority", description: "Priority from 1 to 10, or null to clear it", required: false },
|
|
1819
|
-
{ name: "color", description: "Card colour, or null to clear it", required: false },
|
|
1820
|
-
{ name: "groups", description: "Labels, as a list of strings. Replaces the current ones.", required: false },
|
|
1821
|
-
{ name: "deadline", description: "Due date as YYYY-MM-DD, or null to clear it", required: false },
|
|
1822
|
-
{ name: "assignees", description: "User ids to assign, as a list. Replaces the current ones; use kanban_list_members to find the ids.", required: false }
|
|
1823
|
-
],
|
|
1824
|
-
buildInput: (p) => ({
|
|
1825
|
-
boardId: p.boardId,
|
|
1826
|
-
cardId: p.cardId,
|
|
1827
|
-
...p.title !== void 0 ? { title: p.title } : {},
|
|
1828
|
-
...p.content !== void 0 ? { content: p.content } : {},
|
|
1829
|
-
// Tout arrive en chaîne : un modèle n'écrit pas des nombres, des listes
|
|
1830
|
-
// ni des `null` typés. `clearable` distingue « efface » de « ne touche
|
|
1831
|
-
// pas » — sans quoi vider une couleur serait impossible à exprimer, et
|
|
1832
|
-
// changer un titre risquerait de vider le reste.
|
|
1833
|
-
...clearable(p.priority, (v) => Number(v), "priority"),
|
|
1834
|
-
...clearable(p.color, (v) => v, "color"),
|
|
1835
|
-
...clearable(p.deadline, (v) => v, "deadline"),
|
|
1836
|
-
...p.groups !== void 0 ? { groups: toList(p.groups) } : {},
|
|
1837
|
-
...p.assignees !== void 0 ? { assignees: toList(p.assignees) } : {}
|
|
1838
|
-
})
|
|
1839
|
-
},
|
|
1840
|
-
{
|
|
1841
|
-
name: "kanban_set_card_archived",
|
|
1842
|
-
description: "Archive a kanban card (it leaves the board but is not deleted) or bring it back. An unarchived card returns to the column it left.",
|
|
1843
|
-
route: "kanban.setCardArchived",
|
|
1844
|
-
method: "POST",
|
|
1845
|
-
params: [
|
|
1846
|
-
{ name: "boardId", description: "The kanban board ID", required: true },
|
|
1847
|
-
{ name: "cardId", description: "The card ID", required: true },
|
|
1848
|
-
{ name: "archived", description: "true to archive, false to bring the card back", required: true }
|
|
1849
|
-
],
|
|
1850
|
-
buildInput: (p) => ({ boardId: p.boardId, cardId: p.cardId, archived: p.archived === "true" })
|
|
1851
|
-
},
|
|
1852
|
-
{
|
|
1853
|
-
name: "kanban_list_members",
|
|
1854
|
-
description: "List the people of the organization who can be assigned to a card, with their ids. Needed before assigning anyone with kanban_update_card.",
|
|
1855
|
-
route: "kanban.listMembers",
|
|
1856
|
-
method: "GET",
|
|
1857
|
-
params: [
|
|
1858
|
-
{ name: "organizationId", description: "Which organization. Optional: needed only when the account belongs to several and the error message asks for it.", required: false }
|
|
1859
|
-
],
|
|
1860
|
-
buildInput: (p) => p.organizationId ? { organizationId: p.organizationId } : {}
|
|
1861
|
-
},
|
|
1862
|
-
// ─── Lecture seule ───────────────────────────────────────────────────────
|
|
1863
|
-
//
|
|
1864
|
-
// Tout ce qui se consulte sans rien changer. Les écritures s'ajoutent au cas
|
|
1865
|
-
// par cas : aujourd'hui les services et leurs variables, plus bas.
|
|
1866
|
-
//
|
|
1867
|
-
// Volontairement absentes, et à ne pas ajouter sans y regarder à deux fois,
|
|
1868
|
-
// parce qu'elles rendent un secret : `environment.list` / `get` / `resolve` /
|
|
1869
|
-
// `resolveFlat` (un `Environment` porte ses variables, valeurs comprises),
|
|
1870
|
-
// `apiKeys.list` / `reveal`, `setup.getEncryptionKeyHex` et `getOrgKeys`
|
|
1871
|
-
// (clés de chiffrement), `settings.get` et `settings.listAIProviders` (clés
|
|
1872
|
-
// de fournisseurs d'IA — `settings.getPaths` et `getNetwork`, eux, ne
|
|
1873
|
-
// rendent que des chemins et une adresse, d'où leur présence ci-dessous),
|
|
1874
|
-
// `cloudAuth.session`, `agent.getPassthroughEnv`. Un agent n'a pas besoin de
|
|
1875
|
-
// ces valeurs pour travailler, et ce qu'il lit finit dans une transcription.
|
|
1876
|
-
//
|
|
1877
|
-
// La seule exception est `service.getEnv` (`get_service_env` ci-dessous),
|
|
1878
|
-
// et elle n'en est une que parce qu'elle filtre : les variables publiques, et
|
|
1879
|
-
// les seuls secrets que leur auteur a partagés avec les IA (`isSharedWithAi`
|
|
1880
|
-
// — « Lisible par l'IA », ou l'injection, qui vaut la lecture). Un secret non
|
|
1881
|
-
// partagé n'y apparaît pas, pas même par son nom.
|
|
1882
|
-
{
|
|
1883
|
-
name: "list_processes",
|
|
1884
|
-
description: "List every process across all agents with its current state (running, stopped, exit code, uptime). Use this to see the whole machine at once, rather than calling get_service_status service by service. An agent only learns about a service when it is first started, so this lists what is deployed, not what is configured: an empty list means nothing has been started, not that you cannot see them \u2014 list_services shows what exists.",
|
|
1885
|
-
route: "process.list",
|
|
1886
|
-
method: "GET",
|
|
1887
|
-
params: [],
|
|
1888
|
-
buildInput: () => void 0
|
|
1889
|
-
},
|
|
1890
|
-
{
|
|
1891
|
-
name: "get_service",
|
|
1892
|
-
transformOutput: maskServices,
|
|
1893
|
-
description: "Read the full configuration of one service: its commands, ports, groups, runner, log sources and restart policy. list_services gives the ids.",
|
|
1894
|
-
route: "service.get",
|
|
1895
|
-
method: "GET",
|
|
1896
|
-
params: [
|
|
1897
|
-
{ name: "serviceId", description: "The service ID", required: true }
|
|
1898
|
-
],
|
|
1899
|
-
buildInput: (p) => ({ id: p.serviceId })
|
|
1900
|
-
},
|
|
1901
|
-
{
|
|
1902
|
-
name: "get_service_env",
|
|
1903
|
-
description: 'Read the environment variables a service runs with, and the project\'s variable inventory. `env` lists every key the service receives; a value reads "***" when it is a secret whose author did not share it with AI (sharing = ticking "AI read", or injecting it into the agents\' environment, which grants reading too). `envSecrets` says which keys are secret, `hiddenKeys` which ones are masked. A masked key is set on the service: never treat it as missing, never suggest adding it, and never pass "***" as a value \u2014 ask the user for it. `variables` describes every variable of the project \u2014 which environment defines it and its scope, its description, its flags, whether this machine overrides it, and `inService: false` for the ones this service does not receive. Use it to say where a variable must be changed instead of guessing. On an overridden variable, `sharedValue` is what the machine-local override replaces (the team\'s value, masked as "***" when unreadable); no other value appears there. This is the resolver: the values are what the service would receive \u2014 inheritance, {{environment.KEY}} references, this machine\'s overrides and server built-ins applied; agent built-ins stay as written until the service starts. Use it to check a variable you just composed. `unresolvedReferences` lists references nothing will fill (a typo, a variable that does not exist): fix them.',
|
|
1904
|
-
route: "service.getEnv",
|
|
1905
|
-
method: "GET",
|
|
1906
|
-
params: [
|
|
1907
|
-
{ name: "serviceId", description: "The service ID", required: true },
|
|
1908
|
-
{ name: "environmentId", description: "A global environment of the project to resolve in instead of the active one, without activating it (from list_environments). Omit for the active one.", required: false }
|
|
1909
|
-
],
|
|
1910
|
-
buildInput: (p) => ({ id: p.serviceId, ...p.environmentId ? { environmentId: p.environmentId } : {} })
|
|
1911
|
-
},
|
|
1912
|
-
// ─── Écrire un service et ses variables ────────────────────────────────────
|
|
1913
|
-
//
|
|
1914
|
-
// Les variables s'écrivent à l'aveugle (`environment.setVariableBlind`) : une
|
|
1915
|
-
// IA remplace la valeur d'un secret sans la lire, et ne peut ni le rendre
|
|
1916
|
-
// public, ni le partager avec les IA. La règle tient côté serveur, dans la
|
|
1917
|
-
// route ; ces outils ne passent jamais par une route qui renvoie des valeurs
|
|
1918
|
-
// (`upsertVariable`, `removeVariable`, `update` rendent l'environnement entier).
|
|
1919
|
-
{
|
|
1920
|
-
name: "create_service",
|
|
1921
|
-
transformOutput: maskServices,
|
|
1922
|
-
description: "Create a service, native or Docker, in a project: it joins the project and gets its environments in the same call. Pass the projectId your context names. Projects are synced between machines, so write paths relative to the project root and put machine-specific values (a device serial, a local token) in environment variables, referenced as $NAME in commands \u2014 see the cwd, commands and shortcuts parameters. Then set its variables with set_environment_variable.",
|
|
1923
|
-
route: "service.create",
|
|
1924
|
-
method: "POST",
|
|
1925
|
-
params: [
|
|
1926
|
-
{ name: "name", description: "The service name", required: true },
|
|
1927
|
-
{ name: "projectId", description: "The project it joins: the one your context names, or another from list_projects. Without it, the service belongs to no project.", required: false },
|
|
1928
|
-
{ name: "orgId", description: "Its organization. Needed only when the same project id exists in several.", required: false },
|
|
1929
|
-
...CREATE_SERVICE_PARAMS
|
|
1930
|
-
],
|
|
1931
|
-
buildInput: (p) => {
|
|
1932
|
-
const fields = serviceFields(p, CREATE_SERVICE_PARAMS);
|
|
1933
|
-
if (fields["runner"] === "docker" && !fields["dockerConfig"]) {
|
|
1934
|
-
throw new Error('dockerConfig: required with runner "docker" \u2014 give an "image" or a "dockerfile"');
|
|
1935
|
-
}
|
|
1936
|
-
return {
|
|
1937
|
-
name: p.name,
|
|
1938
|
-
...fields,
|
|
1939
|
-
...p.projectId ? { projectId: p.projectId } : {},
|
|
1940
|
-
...p.orgId ? { orgId: p.orgId } : {}
|
|
1941
|
-
};
|
|
1942
|
-
}
|
|
1943
|
-
},
|
|
1944
|
-
{
|
|
1945
|
-
name: "add_service_to_project",
|
|
1946
|
-
description: "Add an existing service to a project, and create its environments \u2014 one for each of the project's environments, where its own variables go. Not needed after create_service, which already does it; calling it again changes nothing.",
|
|
1947
|
-
route: "project.addService",
|
|
1948
|
-
method: "POST",
|
|
1949
|
-
params: [
|
|
1950
|
-
{ name: "projectId", description: "The project ID, from list_projects", required: true },
|
|
1951
|
-
{ name: "serviceId", description: "The service ID", required: true },
|
|
1952
|
-
{ name: "orgId", description: "Its organization. Needed only when the same project id exists in several.", required: false }
|
|
1953
|
-
],
|
|
1954
|
-
buildInput: (p) => ({ projectId: p.projectId, serviceId: p.serviceId, ...p.orgId ? { orgId: p.orgId } : {} })
|
|
1955
|
-
},
|
|
1956
|
-
{
|
|
1957
|
-
name: "update_service",
|
|
1958
|
-
transformOutput: maskServices,
|
|
1959
|
-
description: "Change any part of a service configuration \u2014 commands, ports, health check, restart policy, shortcuts, log sources, whether it runs natively or in Docker (runner, dockerConfig)\u2026 Only the fields you pass change; a list or an object you pass replaces the current one whole. Read the service first with get_service.",
|
|
1960
|
-
route: "service.update",
|
|
1961
|
-
method: "POST",
|
|
1962
|
-
params: [
|
|
1963
|
-
{ name: "serviceId", description: "The service ID", required: true },
|
|
1964
|
-
{ name: "name", description: "New name", required: false },
|
|
1965
|
-
{ name: "orgId", description: "Move the service to this organization vault. Leave unset unless asked.", required: false, schema: z3.string().max(128).nullable() },
|
|
1966
|
-
...UPDATE_SERVICE_PARAMS
|
|
1967
|
-
],
|
|
1968
|
-
buildInput: (p) => ({
|
|
1969
|
-
id: p.serviceId,
|
|
1970
|
-
...p.name ? { name: p.name } : {},
|
|
1971
|
-
...p.orgId !== void 0 ? { orgId: parseTyped("orgId", z3.string().max(128).nullable(), p.orgId) } : {},
|
|
1972
|
-
...serviceFields(p, UPDATE_SERVICE_PARAMS)
|
|
1973
|
-
})
|
|
1974
|
-
},
|
|
1975
|
-
{
|
|
1976
|
-
name: "list_environments",
|
|
1977
|
-
description: "List the environments of a project, with no value: each one's id, name and scope (global = shared by the whole project, service = one service's own variables), which global environment is active, which service a service environment belongs to and which global environment activates it, its parents (it inherits their variables), `isolated` when a service environment does not inherit from its global one, and the variables it defines with their flags. Empty environments are listed too \u2014 a service you just created has one per global environment, where its own variables go. Use it to find the environmentId before set_environment_variable.",
|
|
1978
|
-
route: "environment.listForAi",
|
|
1979
|
-
method: "GET",
|
|
1980
|
-
params: [
|
|
1981
|
-
{ name: "projectId", description: "The project ID: the one your context names, or another from list_projects", required: true },
|
|
1982
|
-
{ name: "orgId", description: "Its organization. Needed only when the same project id exists in several.", required: false }
|
|
1983
|
-
],
|
|
1984
|
-
buildInput: (p) => ({ projectId: p.projectId, ...p.orgId ? { orgId: p.orgId } : {} })
|
|
1985
|
-
},
|
|
1986
|
-
{
|
|
1987
|
-
name: "resolve_environment",
|
|
1988
|
-
description: "Resolve one environment alone \u2014 its variables with those inherited from its parents and this machine's overrides \u2014 as an AI may read it: secrets not shared with AI read \"***\". Built-in references ({{service.*}}, {{services.*}}, {{agent.*}}\u2026) are NOT filled, since they depend on the service that receives the variable; use get_service_env for a service's final values. `unresolvedReferences` lists references nothing will fill: a misspelled {{environment.KEY}}, or a name that is no built-in.",
|
|
1989
|
-
route: "environment.resolveForAi",
|
|
1990
|
-
method: "GET",
|
|
1991
|
-
params: [
|
|
1992
|
-
{ name: "environmentId", description: "The environment, from list_environments", required: true }
|
|
1993
|
-
],
|
|
1994
|
-
buildInput: (p) => ({ environmentId: p.environmentId })
|
|
1995
|
-
},
|
|
1996
|
-
{
|
|
1997
|
-
name: "set_environment_variable",
|
|
1998
|
-
description: `Create or change an environment variable, by name, in one environment \u2014 global or service, same rules for both. Find the environmentId with list_environments. Where it belongs: a value shared by several services (a database host, an API base URL) goes ONCE in the active global environment; services inherit it. A value only one service needs goes in that service's environment (scope service, activated by the active global one), where it also overrides a global variable of the same name. Never hardcode what Runeya already knows: a service's own port is {{service.PORT}}, its own URL {{service.URL}}, another service's address {{services.<slug>.URL}} \u2014 a copied "3210" or "http://localhost:3211" breaks as soon as a port changes or the project runs on another machine. If the port a variable needs is not declared on its service, declare it there (update_service, ports) instead of writing the number. When a written value contains a port or URL of a project service, the response lists \`suggestions\`: rewrite the value with those references. Compose instead of copying: a value can reference another variable of the resolved environment as {{environment.KEY}} (e.g. "postgres://{{environment.DB_USER}}@{{environment.DB_HOST}}:5432"). ${BUILTINS_HELP} After writing, check the result with get_service_env (a service, optionally in another global environment) or resolve_environment (an environment alone). References are substituted in one pass: a reference to a variable whose own value is only a reference may stay unexpanded, so point at the variable that holds the actual value. $NAME is not a reference here: it only expands inside a shell command. pre and post are concatenated around the value, each resolved separately. You can replace a secret's value without reading it, and create a variable as secret. You cannot make a secret public nor make it readable by AI: that stays the user's decision. Never pass "***" as a value. The response never contains the value.`,
|
|
1999
|
-
route: "environment.setVariableBlind",
|
|
2000
|
-
method: "POST",
|
|
2001
|
-
params: [
|
|
2002
|
-
{ name: "environmentId", description: "The environment that holds the variable, from list_environments", required: true },
|
|
2003
|
-
{ name: "key", description: "The variable name, e.g. DATABASE_URL", required: true },
|
|
2004
|
-
{ name: "value", description: "The new value, possibly with {{environment.KEY}} references. Omit to leave it unchanged.", required: false },
|
|
2005
|
-
{ name: "pre", description: 'Text put before the value (e.g. "https://"). Omit to leave it unchanged.', required: false },
|
|
2006
|
-
{ name: "post", description: 'Text put after the value (e.g. "/api"). Omit to leave it unchanged.', required: false },
|
|
2007
|
-
{ name: "isSecret", description: '"true" to store it as a secret. On an existing public variable, makes it secret.', required: false },
|
|
2008
|
-
{ name: "description", description: "What the variable is for, in markdown", required: false }
|
|
2009
|
-
],
|
|
2010
|
-
buildInput: (p) => ({
|
|
2011
|
-
environmentId: p.environmentId,
|
|
2012
|
-
key: p.key,
|
|
2013
|
-
...p.value !== void 0 ? { value: p.value } : {},
|
|
2014
|
-
...p.pre !== void 0 ? { pre: p.pre } : {},
|
|
2015
|
-
...p.post !== void 0 ? { post: p.post } : {},
|
|
2016
|
-
...p.isSecret !== void 0 ? { isSecret: toBool(p.isSecret) } : {},
|
|
2017
|
-
...p.description !== void 0 ? { description: p.description } : {}
|
|
2018
|
-
})
|
|
2019
|
-
},
|
|
2020
|
-
{
|
|
2021
|
-
name: "set_environment_inheritance",
|
|
2022
|
-
description: "Switch whether a service environment inherits the variables of the global environment that activates it (the default), or is isolated from it and receives only its own variables. list_environments shows this as `isolated`. Isolating a service is unusual: it loses every shared variable of the project. Do it only when the user asks, and never to work around a variable \u2014 override it in the service environment instead.",
|
|
2023
|
-
route: "environment.setInheritanceBlind",
|
|
2024
|
-
method: "POST",
|
|
2025
|
-
params: [
|
|
2026
|
-
{ name: "environmentId", description: "A service environment (scope service), from list_environments", required: true },
|
|
2027
|
-
{ name: "inherit", description: '"true" to inherit from the global environment, "false" to isolate', required: true, schema: z3.boolean() }
|
|
2028
|
-
],
|
|
2029
|
-
buildInput: (p) => ({ environmentId: p.environmentId, inherit: toBool(p.inherit ?? "true") })
|
|
2030
|
-
},
|
|
2031
|
-
{
|
|
2032
|
-
name: "remove_environment_variable",
|
|
2033
|
-
description: "Delete an environment variable, by name, from one environment. Its value is lost.",
|
|
2034
|
-
route: "environment.removeVariableBlind",
|
|
2035
|
-
method: "POST",
|
|
2036
|
-
params: [
|
|
2037
|
-
{ name: "environmentId", description: "The environment that holds the variable", required: true },
|
|
2038
|
-
{ name: "key", description: "The variable name", required: true }
|
|
2039
|
-
],
|
|
2040
|
-
buildInput: (p) => ({ environmentId: p.environmentId, key: p.key }),
|
|
2041
|
-
destructive: true
|
|
2042
|
-
},
|
|
2043
|
-
{
|
|
2044
|
-
name: "get_service_metrics",
|
|
2045
|
-
description: "Read the CPU and memory currently used by a running service.",
|
|
2046
|
-
route: "process.metrics",
|
|
2047
|
-
method: "GET",
|
|
2048
|
-
params: [
|
|
2049
|
-
{ name: "serviceId", description: "The service ID", required: true },
|
|
2050
|
-
{ name: "agentId", description: "The agent ID hosting this service", required: true }
|
|
2051
|
-
],
|
|
2052
|
-
buildInput: (p) => ({ agentId: p.agentId, processId: p.serviceId })
|
|
2053
|
-
},
|
|
2054
|
-
{
|
|
2055
|
-
name: "list_agents",
|
|
2056
|
-
description: "List the agents connected to this Runeya, with their status and whether they run on this machine. Passphrases are never returned.",
|
|
2057
|
-
route: "agent.list",
|
|
2058
|
-
method: "GET",
|
|
2059
|
-
params: [],
|
|
2060
|
-
buildInput: () => void 0
|
|
2061
|
-
},
|
|
2062
|
-
{
|
|
2063
|
-
name: "list_projects",
|
|
2064
|
-
description: "List the projects known to this Runeya, with their ids and the services they hold.",
|
|
2065
|
-
route: "project.list",
|
|
2066
|
-
method: "GET",
|
|
2067
|
-
params: [],
|
|
2068
|
-
buildInput: () => void 0
|
|
2069
|
-
},
|
|
2070
|
-
{
|
|
2071
|
-
name: "get_project",
|
|
2072
|
-
description: "Read one project: its services, its active environment, its organization.",
|
|
2073
|
-
route: "project.get",
|
|
2074
|
-
method: "GET",
|
|
2075
|
-
params: [
|
|
2076
|
-
{ name: "projectId", description: "The project ID, from list_projects", required: true },
|
|
2077
|
-
{ name: "orgId", description: "Its organization. Needed only when the same project id exists in several.", required: false }
|
|
2078
|
-
],
|
|
2079
|
-
buildInput: (p) => ({ id: p.projectId, ...p.orgId ? { orgId: p.orgId } : {} })
|
|
2080
|
-
},
|
|
2081
|
-
{
|
|
2082
|
-
name: "list_project_paths",
|
|
2083
|
-
description: "Where each project lives on this machine. Use it to find the directory to work in before reading or editing files.",
|
|
2084
|
-
route: "project.localPaths",
|
|
2085
|
-
method: "GET",
|
|
2086
|
-
params: [],
|
|
2087
|
-
buildInput: () => void 0
|
|
2088
|
-
},
|
|
2089
|
-
{
|
|
2090
|
-
name: "git_status",
|
|
2091
|
-
description: "Read the git working tree of a service or a project: staged, modified and untracked files, current branch, ahead/behind counts.",
|
|
2092
|
-
route: "git.status",
|
|
2093
|
-
method: "GET",
|
|
2094
|
-
params: [
|
|
2095
|
-
{ name: "serviceId", description: "Target the repository of this service", required: false },
|
|
2096
|
-
{ name: "projectId", description: "Target the repository of this project, when no service is given", required: false },
|
|
2097
|
-
{ name: "agentId", description: "The agent hosting the repository", required: false },
|
|
2098
|
-
{ name: "orgId", description: "Its organization, when a project id alone is ambiguous", required: false }
|
|
2099
|
-
],
|
|
2100
|
-
buildInput: (p) => gitTarget(p)
|
|
2101
|
-
},
|
|
2102
|
-
{
|
|
2103
|
-
name: "git_log",
|
|
2104
|
-
description: "Read the recent commits of a service or project repository: hash, author, date, message.",
|
|
2105
|
-
route: "git.log",
|
|
2106
|
-
method: "GET",
|
|
2107
|
-
params: [
|
|
2108
|
-
{ name: "serviceId", description: "Target the repository of this service", required: false },
|
|
2109
|
-
{ name: "projectId", description: "Target the repository of this project, when no service is given", required: false },
|
|
2110
|
-
{ name: "agentId", description: "The agent hosting the repository", required: false },
|
|
2111
|
-
{ name: "orgId", description: "Its organization, when a project id alone is ambiguous", required: false },
|
|
2112
|
-
{ name: "limit", description: "How many commits to return (1-200). Default: 50.", required: false }
|
|
2113
|
-
],
|
|
2114
|
-
buildInput: (p) => ({ ...gitTarget(p), ...p.limit ? { limit: Number(p.limit) } : {} })
|
|
2115
|
-
},
|
|
2116
|
-
{
|
|
2117
|
-
name: "git_branches",
|
|
2118
|
-
description: "List the local and remote branches of a service or project repository.",
|
|
2119
|
-
route: "git.branches",
|
|
2120
|
-
method: "GET",
|
|
2121
|
-
params: [
|
|
2122
|
-
{ name: "serviceId", description: "Target the repository of this service", required: false },
|
|
2123
|
-
{ name: "projectId", description: "Target the repository of this project, when no service is given", required: false },
|
|
2124
|
-
{ name: "agentId", description: "The agent hosting the repository", required: false },
|
|
2125
|
-
{ name: "orgId", description: "Its organization, when a project id alone is ambiguous", required: false }
|
|
2126
|
-
],
|
|
2127
|
-
buildInput: (p) => gitTarget(p)
|
|
2128
|
-
},
|
|
2129
|
-
{
|
|
2130
|
-
name: "git_diff",
|
|
2131
|
-
description: "Read the diff of a service or project repository \u2014 everything, or one file. Use it to see what changed before describing or reviewing it.",
|
|
2132
|
-
route: "git.diff",
|
|
2133
|
-
method: "GET",
|
|
2134
|
-
params: [
|
|
2135
|
-
{ name: "serviceId", description: "Target the repository of this service", required: false },
|
|
2136
|
-
{ name: "projectId", description: "Target the repository of this project, when no service is given", required: false },
|
|
2137
|
-
{ name: "agentId", description: "The agent hosting the repository", required: false },
|
|
2138
|
-
{ name: "orgId", description: "Its organization, when a project id alone is ambiguous", required: false },
|
|
2139
|
-
{ name: "path", description: "Restrict the diff to this file path", required: false },
|
|
2140
|
-
{ name: "staged", description: 'Set to "true" to read the staged diff instead of the working tree', required: false },
|
|
2141
|
-
{ name: "untracked", description: 'Set to "true" to include untracked files', required: false }
|
|
2142
|
-
],
|
|
2143
|
-
buildInput: (p) => ({
|
|
2144
|
-
...gitTarget(p),
|
|
2145
|
-
...p.path ? { path: p.path } : {},
|
|
2146
|
-
...p.staged !== void 0 ? { staged: p.staged === "true" } : {},
|
|
2147
|
-
...p.untracked !== void 0 ? { untracked: p.untracked === "true" } : {}
|
|
2148
|
-
})
|
|
2149
|
-
},
|
|
2150
|
-
{
|
|
2151
|
-
name: "list_docs",
|
|
2152
|
-
description: "List the markdown documentation files of a scope, as a tree. Pair it with read_doc to answer from the project's own docs rather than from guesswork.",
|
|
2153
|
-
route: "wiki.list",
|
|
2154
|
-
method: "GET",
|
|
2155
|
-
params: [
|
|
2156
|
-
{ name: "scope", description: `"global" for this Runeya, or "service:<serviceId>" for one service's repository`, required: true }
|
|
2157
|
-
],
|
|
2158
|
-
buildInput: (p) => ({ scope: p.scope })
|
|
2159
|
-
},
|
|
2160
|
-
{
|
|
2161
|
-
name: "read_doc",
|
|
2162
|
-
description: "Read one markdown documentation file. Paths come from list_docs; only .md and .markdown can be read.",
|
|
2163
|
-
route: "wiki.read",
|
|
2164
|
-
method: "GET",
|
|
2165
|
-
params: [
|
|
2166
|
-
{ name: "scope", description: `"global" for this Runeya, or "service:<serviceId>" for one service's repository`, required: true },
|
|
2167
|
-
{ name: "path", description: "Relative path of the file, as returned by list_docs", required: true }
|
|
2168
|
-
],
|
|
2169
|
-
buildInput: (p) => ({ scope: p.scope, path: p.path })
|
|
2170
|
-
},
|
|
2171
|
-
{
|
|
2172
|
-
name: "get_server_logs",
|
|
2173
|
-
description: "Read Runeya's own recent log entries. Use it to explain why Runeya itself misbehaved \u2014 not to read a service's output, which is get_logs.",
|
|
2174
|
-
route: "diagnostics.recentLogs",
|
|
2175
|
-
method: "GET",
|
|
2176
|
-
params: [
|
|
2177
|
-
{ name: "limit", description: "How many entries to return (1-2000). Default: 500.", required: false }
|
|
2178
|
-
],
|
|
2179
|
-
buildInput: (p) => p.limit ? { limit: Number(p.limit) } : void 0
|
|
2180
|
-
},
|
|
2181
|
-
{
|
|
2182
|
-
name: "get_kanban_queue_state",
|
|
2183
|
-
description: "Read the state of the kanban task queue: what is running, what is waiting. Use it before queuing work, or to explain why a card has not started.",
|
|
2184
|
-
route: "kanbanQueue.getState",
|
|
2185
|
-
method: "GET",
|
|
2186
|
-
params: [],
|
|
2187
|
-
buildInput: () => void 0
|
|
2188
|
-
},
|
|
2189
|
-
{
|
|
2190
|
-
name: "export_project",
|
|
2191
|
-
description: "Read a project and every service it holds in one call, with secrets stripped out. Cheaper and safer than calling get_service for each service \u2014 prefer it whenever you need the whole picture of a project.",
|
|
2192
|
-
route: "project.export",
|
|
2193
|
-
method: "GET",
|
|
2194
|
-
params: [
|
|
2195
|
-
{ name: "projectId", description: "The project ID, from list_projects", required: true },
|
|
2196
|
-
{ name: "orgId", description: "Its organization. Needed only when the same project id exists in several.", required: false }
|
|
2197
|
-
],
|
|
2198
|
-
buildInput: (p) => ({ id: p.projectId, ...p.orgId ? { orgId: p.orgId } : {} })
|
|
2199
|
-
},
|
|
2200
|
-
{
|
|
2201
|
-
name: "git_remotes",
|
|
2202
|
-
description: "List the git remotes of a service or project repository, with their URLs.",
|
|
2203
|
-
route: "git.remotes",
|
|
2204
|
-
method: "GET",
|
|
2205
|
-
params: [
|
|
2206
|
-
{ name: "serviceId", description: "Target the repository of this service", required: false },
|
|
2207
|
-
{ name: "projectId", description: "Target the repository of this project, when no service is given", required: false },
|
|
2208
|
-
{ name: "agentId", description: "The agent hosting the repository", required: false },
|
|
2209
|
-
{ name: "orgId", description: "Its organization, when a project id alone is ambiguous", required: false }
|
|
2210
|
-
],
|
|
2211
|
-
buildInput: (p) => gitTarget(p)
|
|
2212
|
-
},
|
|
2213
|
-
{
|
|
2214
|
-
name: "git_stash_list",
|
|
2215
|
-
description: "List the stashes of a service or project repository. Useful before assuming work was lost: it may simply be stashed.",
|
|
2216
|
-
route: "git.stashList",
|
|
2217
|
-
method: "GET",
|
|
2218
|
-
params: [
|
|
2219
|
-
{ name: "serviceId", description: "Target the repository of this service", required: false },
|
|
2220
|
-
{ name: "projectId", description: "Target the repository of this project, when no service is given", required: false },
|
|
2221
|
-
{ name: "agentId", description: "The agent hosting the repository", required: false },
|
|
2222
|
-
{ name: "orgId", description: "Its organization, when a project id alone is ambiguous", required: false }
|
|
2223
|
-
],
|
|
2224
|
-
buildInput: (p) => gitTarget(p)
|
|
2225
|
-
},
|
|
2226
|
-
{
|
|
2227
|
-
name: "get_runeya_version",
|
|
2228
|
-
description: "Read the version of Runeya running here, the latest published one, and whether this install is up to date. Use it before blaming a bug on the code: the machine may simply be behind.",
|
|
2229
|
-
route: "health.versionInfo",
|
|
2230
|
-
method: "GET",
|
|
2231
|
-
params: [],
|
|
2232
|
-
buildInput: () => void 0
|
|
2233
|
-
},
|
|
2234
|
-
{
|
|
2235
|
-
name: "get_runeya_paths",
|
|
2236
|
-
description: "Read where Runeya keeps its things on this machine: its launch directory, its data directory, and the machine-wide directory holding cloud projects.",
|
|
2237
|
-
route: "settings.getPaths",
|
|
2238
|
-
method: "GET",
|
|
2239
|
-
params: [],
|
|
2240
|
-
buildInput: () => void 0
|
|
2241
|
-
},
|
|
2242
|
-
{
|
|
2243
|
-
name: "get_network_info",
|
|
2244
|
-
description: "Read where this Runeya listens (host and port) and whether it is reachable from the local network, with the URLs to use. Use it to give someone a working link instead of guessing one.",
|
|
2245
|
-
route: "settings.getNetwork",
|
|
2246
|
-
method: "GET",
|
|
2247
|
-
params: [],
|
|
2248
|
-
buildInput: () => void 0
|
|
2249
|
-
},
|
|
2250
|
-
{
|
|
2251
|
-
name: "list_runeya_instances",
|
|
2252
|
-
description: "List every Runeya answering on this machine, this one included, with its port and directory. Use it when a port is taken or when two instances seem to disagree.",
|
|
2253
|
-
route: "diagnostics.instances",
|
|
2254
|
-
method: "GET",
|
|
2255
|
-
params: [],
|
|
2256
|
-
buildInput: () => void 0
|
|
2257
|
-
},
|
|
2258
|
-
{
|
|
2259
|
-
name: "get_cloud_status",
|
|
2260
|
-
description: "Say whether this Runeya is linked to a cloud account, and to which cloud. A kanban tool failing for lack of a cloud link is explained here.",
|
|
2261
|
-
route: "cloudAuth.status",
|
|
2262
|
-
method: "GET",
|
|
2263
|
-
params: [],
|
|
2264
|
-
buildInput: () => void 0
|
|
2265
|
-
}
|
|
2266
|
-
];
|
|
2267
|
-
|
|
2268
|
-
// ../../packages/ai-capabilities/src/adapters/json-schema.ts
|
|
2269
|
-
import { z as z4 } from "zod";
|
|
2270
|
-
function paramJsonSchema(param) {
|
|
2271
|
-
if (!param.schema) return { type: "string", description: param.description };
|
|
2272
|
-
const { $schema: _ignored, ...schema } = z4.toJSONSchema(param.schema, { io: "input", unrepresentable: "any" });
|
|
2273
|
-
return { ...schema, description: param.description };
|
|
2274
|
-
}
|
|
2275
|
-
|
|
2276
|
-
// ../../packages/ai-capabilities/src/adapters/anthropic.ts
|
|
2277
|
-
function buildAnthropicTools(capabilities) {
|
|
2278
|
-
return capabilities.map((cap) => ({
|
|
2279
|
-
name: cap.name,
|
|
2280
|
-
description: cap.description,
|
|
2281
|
-
input_schema: {
|
|
2282
|
-
type: "object",
|
|
2283
|
-
properties: Object.fromEntries(
|
|
2284
|
-
cap.params.map((p) => [p.name, paramJsonSchema(p)])
|
|
2285
|
-
),
|
|
2286
|
-
required: cap.params.filter((p) => p.required).map((p) => p.name)
|
|
2287
|
-
}
|
|
2288
|
-
}));
|
|
2289
|
-
}
|
|
2290
|
-
|
|
2291
|
-
// ../../packages/ai-capabilities/src/adapters/openai.ts
|
|
2292
|
-
function buildOpenAITools(capabilities) {
|
|
2293
|
-
return capabilities.map((cap) => ({
|
|
2294
|
-
type: "function",
|
|
2295
|
-
function: {
|
|
2296
|
-
name: cap.name,
|
|
2297
|
-
description: cap.description,
|
|
2298
|
-
parameters: {
|
|
2299
|
-
type: "object",
|
|
2300
|
-
properties: Object.fromEntries(
|
|
2301
|
-
cap.params.map((p) => [p.name, paramJsonSchema(p)])
|
|
2302
|
-
),
|
|
2303
|
-
required: cap.params.filter((p) => p.required).map((p) => p.name)
|
|
2304
|
-
}
|
|
2305
|
-
}
|
|
2306
|
-
}));
|
|
2307
|
-
}
|
|
2308
|
-
|
|
2309
|
-
// ../../packages/ai-capabilities/src/adapters/prompt.ts
|
|
2310
|
-
function buildToolSafetyInstruction(capabilities) {
|
|
2311
|
-
const destructive = capabilities.filter((c) => c.destructive).map((c) => c.name);
|
|
2312
|
-
const withHints = capabilities.filter((c) => c.usageHint);
|
|
2313
|
-
const lines = ["## Tool use safety rules"];
|
|
2314
|
-
if (destructive.length > 0) {
|
|
2315
|
-
lines.push(`CRITICAL: Never call ${destructive.join(", ")} unless the user has EXPLICITLY and UNAMBIGUOUSLY requested it.`);
|
|
2316
|
-
lines.push(`- Only call destructive tools (${destructive.join(", ")}) when the user explicitly uses action words for a specific service.`);
|
|
2317
|
-
}
|
|
2318
|
-
if (withHints.length > 0) {
|
|
2319
|
-
lines.push("");
|
|
2320
|
-
lines.push("## Tool usage hints");
|
|
2321
|
-
for (const cap of withHints) {
|
|
2322
|
-
lines.push(`- ${cap.usageHint}`);
|
|
2323
|
-
}
|
|
2324
|
-
}
|
|
2325
|
-
return lines.join("\n");
|
|
2326
|
-
}
|
|
2327
|
-
|
|
2328
|
-
// ../../packages/ai-capabilities/src/adapters/mcp.ts
|
|
2329
|
-
import { z as z5 } from "zod";
|
|
2330
|
-
function toRawParams(input, cap) {
|
|
2331
|
-
const typed = new Set(cap?.params.filter((p) => p.schema).map((p) => p.name));
|
|
2332
|
-
return Object.fromEntries(Object.entries(input).map(([k, v]) => [
|
|
2333
|
-
k,
|
|
2334
|
-
v === null && typed.has(k) ? "null" : v == null ? void 0 : typeof v === "object" ? JSON.stringify(v) : String(v)
|
|
2335
|
-
]));
|
|
2336
|
-
}
|
|
2337
|
-
|
|
2338
|
-
// ../../packages/ai-capabilities/src/trpc-caller.ts
|
|
2339
|
-
async function callTrpc(baseUrl, route, method, input, authToken, fetchImpl = fetch) {
|
|
2340
|
-
const headers = {
|
|
2341
|
-
"Content-Type": "application/json",
|
|
2342
|
-
"Authorization": `Bearer ${authToken}`
|
|
2343
|
-
};
|
|
2344
|
-
const url = method === "GET" && input ? `${baseUrl}/api/trpc/${route}?input=${encodeURIComponent(JSON.stringify(input))}` : `${baseUrl}/api/trpc/${route}`;
|
|
2345
|
-
const res = await fetchImpl(url, {
|
|
2346
|
-
method,
|
|
2347
|
-
headers,
|
|
2348
|
-
...method === "POST" ? { body: JSON.stringify(input ?? {}) } : {},
|
|
2349
|
-
signal: AbortSignal.timeout(15e3)
|
|
2350
|
-
});
|
|
2351
|
-
const json = await res.json();
|
|
2352
|
-
if (json["error"]) throw new Error(JSON.stringify(json["error"]));
|
|
2353
|
-
const result = json["result"];
|
|
2354
|
-
return result?.["data"] ?? result ?? json;
|
|
2355
|
-
}
|
|
2356
|
-
async function runCapability(cap, baseUrl, params, authToken, fetchImpl = fetch) {
|
|
2357
|
-
const result = await callTrpc(baseUrl, cap.route, cap.method, cap.buildInput(params), authToken, fetchImpl);
|
|
2358
|
-
return cap.transformOutput ? cap.transformOutput(result) : result;
|
|
2359
|
-
}
|
|
2360
|
-
|
|
2361
|
-
// ../../packages/ai-capabilities/src/project-anchor.ts
|
|
2362
|
-
function applyProjectAnchor(cap, params, anchor) {
|
|
2363
|
-
const accepts = (name) => cap.params.some((p) => p.name === name);
|
|
2364
|
-
const anchored = { ...params };
|
|
2365
|
-
const mayAnchorOrg = !params["projectId"] || params["projectId"] === anchor.projectId;
|
|
2366
|
-
if (anchor.projectId && accepts("projectId") && !anchored["projectId"]) {
|
|
2367
|
-
anchored["projectId"] = anchor.projectId;
|
|
2368
|
-
}
|
|
2369
|
-
if (mayAnchorOrg && anchor.orgId && !anchored["orgId"] && accepts("orgId")) {
|
|
2370
|
-
anchored["orgId"] = anchor.orgId;
|
|
2371
|
-
}
|
|
2372
|
-
if (mayAnchorOrg && anchor.orgId && !anchored["organizationId"] && accepts("organizationId")) {
|
|
2373
|
-
anchored["organizationId"] = anchor.orgId;
|
|
2374
|
-
}
|
|
2375
|
-
return anchored;
|
|
2376
|
-
}
|
|
2377
|
-
|
|
2378
634
|
// ../server/src/services/llms-txt-generator.ts
|
|
2379
635
|
function generateAdminLlmsTxt(services, jwt, project) {
|
|
2380
636
|
const lines = [];
|
|
@@ -2584,11 +840,11 @@ async function executeTool(rawName, input, apiKey, authToken, anchor = {}) {
|
|
|
2584
840
|
}
|
|
2585
841
|
|
|
2586
842
|
// ../server/src/trpc/routers/service.ts
|
|
2587
|
-
import { z as
|
|
843
|
+
import { z as z3 } from "zod";
|
|
2588
844
|
import { TRPCError as TRPCError2 } from "@trpc/server";
|
|
2589
845
|
|
|
2590
846
|
// ../server/src/services/audit-service.ts
|
|
2591
|
-
import { appendFile, readFile as
|
|
847
|
+
import { appendFile, readFile as readFile2, stat, rename, unlink, readdir as readdir2, mkdir } from "fs/promises";
|
|
2592
848
|
var AUDIT_FILENAME = "audit.log";
|
|
2593
849
|
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
2594
850
|
var MAX_AGE_DAYS = 30;
|
|
@@ -2608,7 +864,7 @@ var AuditService = class {
|
|
|
2608
864
|
const line = JSON.stringify(record) + "\n";
|
|
2609
865
|
const filepath = this.getFilePath();
|
|
2610
866
|
try {
|
|
2611
|
-
await
|
|
867
|
+
await mkdir(machineRoot(), { recursive: true });
|
|
2612
868
|
await appendFile(filepath, line, "utf-8");
|
|
2613
869
|
await this.rotateIfNeeded();
|
|
2614
870
|
} catch (err) {
|
|
@@ -2621,7 +877,7 @@ var AuditService = class {
|
|
|
2621
877
|
const filepath = this.getFilePath();
|
|
2622
878
|
let lines;
|
|
2623
879
|
try {
|
|
2624
|
-
const content = await
|
|
880
|
+
const content = await readFile2(filepath, "utf-8");
|
|
2625
881
|
lines = content.trim().split("\n").filter(Boolean);
|
|
2626
882
|
} catch {
|
|
2627
883
|
return { entries: [], total: 0 };
|
|
@@ -2977,7 +1233,7 @@ async function resolveServiceVariables(service, opts = {}) {
|
|
|
2977
1233
|
}
|
|
2978
1234
|
return resolvedService;
|
|
2979
1235
|
}
|
|
2980
|
-
var
|
|
1236
|
+
var DockerConfigInput = DockerConfigSchema;
|
|
2981
1237
|
var serviceRouter = router({
|
|
2982
1238
|
/**
|
|
2983
1239
|
* Sans filtre, la réponse couvre TOUT le poste : `serviceStore.list()` rend
|
|
@@ -2994,10 +1250,10 @@ var serviceRouter = router({
|
|
|
2994
1250
|
* silencieux ferait croire au filtrage tout en rendant les services des
|
|
2995
1251
|
* autres organisations.
|
|
2996
1252
|
*/
|
|
2997
|
-
list: protectedProcedure.meta({ openapi: { method: "GET", path: "/services", tags: ["services"], summary: "List services (every project of the machine unless narrowed by project or organization)", protect: true } }).input(
|
|
2998
|
-
projectId:
|
|
2999
|
-
orgId:
|
|
3000
|
-
}).optional()).output(
|
|
1253
|
+
list: protectedProcedure.meta({ openapi: { method: "GET", path: "/services", tags: ["services"], summary: "List services (every project of the machine unless narrowed by project or organization)", protect: true } }).input(z3.object({
|
|
1254
|
+
projectId: z3.string().max(128).optional(),
|
|
1255
|
+
orgId: z3.string().max(128).nullable().optional()
|
|
1256
|
+
}).optional()).output(z3.any()).query(async ({ input, ctx }) => {
|
|
3001
1257
|
const all = await serviceStore.list();
|
|
3002
1258
|
const allowedIds = getAllowedServiceIds(ctx);
|
|
3003
1259
|
let filtered = allowedIds ? all.filter((s) => allowedIds.has(s.id)) : all;
|
|
@@ -3017,7 +1273,7 @@ var serviceRouter = router({
|
|
|
3017
1273
|
effectiveAgentId: await resolveEffectiveAgentId(s)
|
|
3018
1274
|
})));
|
|
3019
1275
|
}),
|
|
3020
|
-
get: protectedProcedure.meta({ openapi: { method: "GET", path: "/services/{id}", tags: ["services"], summary: "Get a service by ID", protect: true } }).input(
|
|
1276
|
+
get: protectedProcedure.meta({ openapi: { method: "GET", path: "/services/{id}", tags: ["services"], summary: "Get a service by ID", protect: true } }).input(z3.object({ id: z3.string().max(128) })).output(z3.any()).query(async ({ input, ctx }) => {
|
|
3021
1277
|
requireServiceAction(ctx, input.id, "service:read");
|
|
3022
1278
|
const service = await serviceStore.get(input.id);
|
|
3023
1279
|
if (!service) {
|
|
@@ -3043,11 +1299,11 @@ var serviceRouter = router({
|
|
|
3043
1299
|
* valeurs** — où chacune est définie, ce que ses cases disent, et si ce
|
|
3044
1300
|
* service la reçoit. Sans lui, un agent lisait l'effet sans la cause.
|
|
3045
1301
|
*/
|
|
3046
|
-
getEnv: protectedProcedure.meta({ openapi: { method: "GET", path: "/services/{id}/env", tags: ["services"], summary: "Get a service's environment variables (unshared secrets masked) plus the project's value-free variable inventory", protect: true } }).input(
|
|
3047
|
-
id:
|
|
1302
|
+
getEnv: protectedProcedure.meta({ openapi: { method: "GET", path: "/services/{id}/env", tags: ["services"], summary: "Get a service's environment variables (unshared secrets masked) plus the project's value-free variable inventory", protect: true } }).input(z3.object({
|
|
1303
|
+
id: z3.string().max(128),
|
|
3048
1304
|
// Un environnement global du projet où résoudre, sans l'activer. Absent : l'actif.
|
|
3049
|
-
environmentId:
|
|
3050
|
-
})).output(
|
|
1305
|
+
environmentId: z3.string().max(128).optional()
|
|
1306
|
+
})).output(z3.any()).query(async ({ input, ctx }) => {
|
|
3051
1307
|
requireServiceAction(ctx, input.id, "service:read");
|
|
3052
1308
|
const service = await serviceStore.get(input.id);
|
|
3053
1309
|
if (!service) {
|
|
@@ -3094,38 +1350,38 @@ var serviceRouter = router({
|
|
|
3094
1350
|
variables
|
|
3095
1351
|
};
|
|
3096
1352
|
}),
|
|
3097
|
-
create: protectedProcedure.meta({ openapi: { method: "POST", path: "/services", tags: ["services"], summary: "Create a new service", protect: true } }).output(
|
|
3098
|
-
|
|
3099
|
-
agentId:
|
|
3100
|
-
name:
|
|
3101
|
-
commands:
|
|
3102
|
-
cwd:
|
|
3103
|
-
ports:
|
|
3104
|
-
groups:
|
|
3105
|
-
description:
|
|
3106
|
-
docsPath:
|
|
3107
|
-
url:
|
|
3108
|
-
localUrls:
|
|
3109
|
-
url:
|
|
3110
|
-
port:
|
|
1353
|
+
create: protectedProcedure.meta({ openapi: { method: "POST", path: "/services", tags: ["services"], summary: "Create a new service", protect: true } }).output(z3.any()).input(
|
|
1354
|
+
z3.object({
|
|
1355
|
+
agentId: z3.string().max(128).optional(),
|
|
1356
|
+
name: z3.string().min(1).max(200),
|
|
1357
|
+
commands: z3.array(ServiceCommandSchema).max(50).optional(),
|
|
1358
|
+
cwd: z3.string().max(4096).optional(),
|
|
1359
|
+
ports: z3.array(z3.number().int().min(1).max(65535)).max(20).optional(),
|
|
1360
|
+
groups: z3.array(z3.string().max(200)).max(20).optional(),
|
|
1361
|
+
description: z3.string().max(2e3).optional(),
|
|
1362
|
+
docsPath: z3.string().max(4096).optional(),
|
|
1363
|
+
url: z3.string().max(2048).optional(),
|
|
1364
|
+
localUrls: z3.array(z3.object({
|
|
1365
|
+
url: z3.string().max(253),
|
|
1366
|
+
port: z3.number().int().min(1).max(65535)
|
|
3111
1367
|
})).max(20).optional(),
|
|
3112
|
-
openapiUrl:
|
|
3113
|
-
git:
|
|
3114
|
-
meta:
|
|
3115
|
-
runner:
|
|
3116
|
-
dockerConfig:
|
|
1368
|
+
openapiUrl: z3.string().max(2048).optional(),
|
|
1369
|
+
git: z3.object({ remote: z3.string().max(2048).optional(), home: z3.string().max(2048).optional() }).optional(),
|
|
1370
|
+
meta: z3.record(z3.string().max(256), z3.string().max(2e3)).optional().refine((obj) => !obj || Object.keys(obj).length <= 100, { message: "Too many meta keys (max 100)" }),
|
|
1371
|
+
runner: z3.enum(["native", "docker"]).optional().default("native"),
|
|
1372
|
+
dockerConfig: DockerConfigInput.optional(),
|
|
3117
1373
|
healthCheck: HealthCheckSchema.optional(),
|
|
3118
|
-
autoRestart:
|
|
3119
|
-
clearLogsOnStart:
|
|
1374
|
+
autoRestart: z3.boolean().optional().default(false),
|
|
1375
|
+
clearLogsOnStart: z3.boolean().optional(),
|
|
3120
1376
|
restartStrategy: RestartStrategySchema.optional(),
|
|
3121
|
-
maxRestarts:
|
|
3122
|
-
restartBackoffMs:
|
|
3123
|
-
orgId:
|
|
3124
|
-
logSources:
|
|
1377
|
+
maxRestarts: z3.number().int().nonnegative().optional(),
|
|
1378
|
+
restartBackoffMs: z3.number().int().positive().optional().default(1e3),
|
|
1379
|
+
orgId: z3.string().max(128).nullable().optional(),
|
|
1380
|
+
logSources: z3.array(LogSourceConfigSchema).max(20).optional(),
|
|
3125
1381
|
// Le projet qui va l'adopter : le service naît dans son coffre.
|
|
3126
|
-
projectId:
|
|
3127
|
-
shortcuts:
|
|
3128
|
-
parserIds:
|
|
1382
|
+
projectId: z3.string().max(128).optional(),
|
|
1383
|
+
shortcuts: z3.array(ServiceShortcutSchema).max(50).optional(),
|
|
1384
|
+
parserIds: z3.array(z3.string().max(128)).max(50).optional()
|
|
3129
1385
|
})
|
|
3130
1386
|
).mutation(async ({ input }) => {
|
|
3131
1387
|
if (input.agentId) {
|
|
@@ -3151,38 +1407,38 @@ var serviceRouter = router({
|
|
|
3151
1407
|
await auditService.log({ userId: "local", action: "create", entityType: "service", entityId: service.id, metadata: { name: input.name } });
|
|
3152
1408
|
return service;
|
|
3153
1409
|
}),
|
|
3154
|
-
update: protectedProcedure.meta({ openapi: { method: "PATCH", path: "/services/{id}", tags: ["services"], summary: "Update a service", protect: true } }).output(
|
|
3155
|
-
|
|
3156
|
-
id:
|
|
3157
|
-
agentId:
|
|
1410
|
+
update: protectedProcedure.meta({ openapi: { method: "PATCH", path: "/services/{id}", tags: ["services"], summary: "Update a service", protect: true } }).output(z3.any()).input(
|
|
1411
|
+
z3.object({
|
|
1412
|
+
id: z3.string().max(128),
|
|
1413
|
+
agentId: z3.string().max(128).nullable().optional(),
|
|
3158
1414
|
// null = clear (inherit from env)
|
|
3159
|
-
name:
|
|
3160
|
-
commands:
|
|
3161
|
-
cwd:
|
|
3162
|
-
ports:
|
|
3163
|
-
groups:
|
|
3164
|
-
description:
|
|
3165
|
-
docsPath:
|
|
3166
|
-
url:
|
|
3167
|
-
localUrls:
|
|
3168
|
-
url:
|
|
3169
|
-
port:
|
|
1415
|
+
name: z3.string().min(1).max(200).optional(),
|
|
1416
|
+
commands: z3.array(ServiceCommandSchema).max(50).optional(),
|
|
1417
|
+
cwd: z3.string().max(4096).nullable().optional(),
|
|
1418
|
+
ports: z3.array(z3.number().int().min(1).max(65535)).max(20).optional(),
|
|
1419
|
+
groups: z3.array(z3.string().max(200)).max(20).optional(),
|
|
1420
|
+
description: z3.string().max(2e3).optional(),
|
|
1421
|
+
docsPath: z3.string().max(4096).optional(),
|
|
1422
|
+
url: z3.string().max(2048).optional(),
|
|
1423
|
+
localUrls: z3.array(z3.object({
|
|
1424
|
+
url: z3.string().max(253),
|
|
1425
|
+
port: z3.number().int().min(1).max(65535)
|
|
3170
1426
|
})).max(20).optional(),
|
|
3171
|
-
openapiUrl:
|
|
3172
|
-
git:
|
|
3173
|
-
meta:
|
|
3174
|
-
runner:
|
|
3175
|
-
dockerConfig:
|
|
1427
|
+
openapiUrl: z3.string().max(2048).optional(),
|
|
1428
|
+
git: z3.object({ remote: z3.string().max(2048).optional(), home: z3.string().max(2048).optional() }).nullable().optional(),
|
|
1429
|
+
meta: z3.record(z3.string().max(256), z3.string().max(2e3)).nullable().optional().refine((obj) => !obj || Object.keys(obj).length <= 100, { message: "Too many meta keys (max 100)" }),
|
|
1430
|
+
runner: z3.enum(["native", "docker"]).optional(),
|
|
1431
|
+
dockerConfig: DockerConfigInput.optional(),
|
|
3176
1432
|
healthCheck: HealthCheckSchema.nullable().optional(),
|
|
3177
|
-
autoRestart:
|
|
3178
|
-
clearLogsOnStart:
|
|
1433
|
+
autoRestart: z3.boolean().optional(),
|
|
1434
|
+
clearLogsOnStart: z3.boolean().optional(),
|
|
3179
1435
|
restartStrategy: RestartStrategySchema.optional(),
|
|
3180
|
-
maxRestarts:
|
|
3181
|
-
restartBackoffMs:
|
|
3182
|
-
orgId:
|
|
3183
|
-
shortcuts:
|
|
3184
|
-
parserIds:
|
|
3185
|
-
logSources:
|
|
1436
|
+
maxRestarts: z3.number().int().nonnegative().optional(),
|
|
1437
|
+
restartBackoffMs: z3.number().int().positive().optional(),
|
|
1438
|
+
orgId: z3.string().max(128).nullable().optional(),
|
|
1439
|
+
shortcuts: z3.array(ServiceShortcutSchema).max(50).optional(),
|
|
1440
|
+
parserIds: z3.array(z3.string().max(128)).max(50).optional(),
|
|
1441
|
+
logSources: z3.array(LogSourceConfigSchema).max(20).optional()
|
|
3186
1442
|
})
|
|
3187
1443
|
).mutation(async ({ input }) => {
|
|
3188
1444
|
const existing = await serviceStore.get(input.id);
|
|
@@ -3238,7 +1494,7 @@ var serviceRouter = router({
|
|
|
3238
1494
|
await auditService.log({ userId: "local", action: "update", entityType: "service", entityId: input.id });
|
|
3239
1495
|
return { ...service, effectiveAgentId: await resolveEffectiveAgentId(service), stoppedForAgentChange };
|
|
3240
1496
|
}),
|
|
3241
|
-
delete: protectedProcedure.meta({ openapi: { method: "DELETE", path: "/services/{id}", tags: ["services"], summary: "Delete a service", protect: true } }).input(
|
|
1497
|
+
delete: protectedProcedure.meta({ openapi: { method: "DELETE", path: "/services/{id}", tags: ["services"], summary: "Delete a service", protect: true } }).input(z3.object({ id: z3.string().max(128) })).output(z3.object({ success: z3.literal(true) })).mutation(async ({ input }) => {
|
|
3242
1498
|
const deleted = await serviceStore.delete(input.id);
|
|
3243
1499
|
if (!deleted) {
|
|
3244
1500
|
throw new TRPCError2({ code: "NOT_FOUND", message: `Service ${input.id} not found` });
|
|
@@ -3246,7 +1502,7 @@ var serviceRouter = router({
|
|
|
3246
1502
|
await auditService.log({ userId: "local", action: "delete", entityType: "service", entityId: input.id });
|
|
3247
1503
|
return { success: true };
|
|
3248
1504
|
}),
|
|
3249
|
-
checkCwd: protectedProcedure.input(
|
|
1505
|
+
checkCwd: protectedProcedure.input(z3.object({ id: z3.string().max(128), checkReachability: z3.boolean().optional() })).query(async ({ input }) => {
|
|
3250
1506
|
const service = await serviceStore.get(input.id);
|
|
3251
1507
|
if (!service) {
|
|
3252
1508
|
throw new TRPCError2({ code: "NOT_FOUND", message: `Service ${input.id} not found` });
|
|
@@ -3279,7 +1535,7 @@ var serviceRouter = router({
|
|
|
3279
1535
|
}
|
|
3280
1536
|
return { exists, canClone, unresolvedVars: [], resolvedCwd, gitMismatch };
|
|
3281
1537
|
}),
|
|
3282
|
-
cloneRepository: protectedProcedure.input(
|
|
1538
|
+
cloneRepository: protectedProcedure.input(z3.object({ id: z3.string().max(128) })).mutation(async ({ input }) => {
|
|
3283
1539
|
const service = await serviceStore.get(input.id);
|
|
3284
1540
|
if (!service) {
|
|
3285
1541
|
throw new TRPCError2({ code: "NOT_FOUND", message: `Service ${input.id} not found` });
|
|
@@ -3462,8 +1718,8 @@ var ScenarioStore = class {
|
|
|
3462
1718
|
var scenarioStore = new ScenarioStore();
|
|
3463
1719
|
|
|
3464
1720
|
// ../server/src/services/scenario-run-store.ts
|
|
3465
|
-
import { mkdir as
|
|
3466
|
-
import { join as
|
|
1721
|
+
import { mkdir as mkdir2, rm, readFile as readFile3 } from "fs/promises";
|
|
1722
|
+
import { join as join3 } from "path";
|
|
3467
1723
|
function sanitizeSlug(value) {
|
|
3468
1724
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "member";
|
|
3469
1725
|
}
|
|
@@ -3479,7 +1735,7 @@ var ScenarioRunStore = class _ScenarioRunStore {
|
|
|
3479
1735
|
}
|
|
3480
1736
|
async initializeConversationWorkspace(conversationId, scenario, userInput) {
|
|
3481
1737
|
const dir = this.getRunDir(conversationId);
|
|
3482
|
-
await
|
|
1738
|
+
await mkdir2(dir, { recursive: true });
|
|
3483
1739
|
await this.saveScenarioSnapshot(conversationId, scenario);
|
|
3484
1740
|
const nodesById = new Map(scenario.nodes.map((p) => [p.id, p]));
|
|
3485
1741
|
const teamMap = new Map(scenario.groups.map((t2) => [t2.id, t2.name]));
|
|
@@ -3504,8 +1760,8 @@ var ScenarioRunStore = class _ScenarioRunStore {
|
|
|
3504
1760
|
"- Use read-only references from linked upstream files as context.",
|
|
3505
1761
|
""
|
|
3506
1762
|
].join("\n");
|
|
3507
|
-
await atomicWriteSecure(
|
|
3508
|
-
await atomicWriteSecure(
|
|
1763
|
+
await atomicWriteSecure(join3(dir, "README.md"), overview);
|
|
1764
|
+
await atomicWriteSecure(join3(dir, "USER_REQUEST.md"), `# User request
|
|
3509
1765
|
|
|
3510
1766
|
${userInput.trim() || "(empty)"}
|
|
3511
1767
|
`);
|
|
@@ -3531,7 +1787,7 @@ ${userInput.trim() || "(empty)"}
|
|
|
3531
1787
|
"- Waiting for assignment.",
|
|
3532
1788
|
""
|
|
3533
1789
|
].join("\n");
|
|
3534
|
-
await atomicWriteSecure(
|
|
1790
|
+
await atomicWriteSecure(join3(dir, fileName), content);
|
|
3535
1791
|
}
|
|
3536
1792
|
}
|
|
3537
1793
|
async deleteRunDir(conversationId) {
|
|
@@ -3591,7 +1847,7 @@ ${userInput.trim() || "(empty)"}
|
|
|
3591
1847
|
if (!runState) return;
|
|
3592
1848
|
try {
|
|
3593
1849
|
const dir = this.getRunDir(conversationId);
|
|
3594
|
-
await
|
|
1850
|
+
await mkdir2(dir, { recursive: true });
|
|
3595
1851
|
const data = {
|
|
3596
1852
|
nodeStates: { ...runState.nodeStates },
|
|
3597
1853
|
nodeOutputs: { ...runState.nodeOutputs },
|
|
@@ -3599,18 +1855,18 @@ ${userInput.trim() || "(empty)"}
|
|
|
3599
1855
|
...Object.keys(runState.nodeAgentIds).length > 0 ? { nodeAgentIds: { ...runState.nodeAgentIds } } : {},
|
|
3600
1856
|
...Object.keys(runState.waitingInputs).length > 0 ? { waitingInputs: { ...runState.waitingInputs } } : {}
|
|
3601
1857
|
};
|
|
3602
|
-
await atomicWriteJsonSecure(
|
|
1858
|
+
await atomicWriteJsonSecure(join3(dir, "run-state.json"), data);
|
|
3603
1859
|
} catch {
|
|
3604
1860
|
}
|
|
3605
1861
|
}
|
|
3606
1862
|
async loadPersistedRunState(conversationId) {
|
|
3607
1863
|
const dir = this.getRunDir(conversationId);
|
|
3608
|
-
return readJsonFileSafe(
|
|
1864
|
+
return readJsonFileSafe(join3(dir, "run-state.json"));
|
|
3609
1865
|
}
|
|
3610
1866
|
async readUserRequest(conversationId) {
|
|
3611
1867
|
try {
|
|
3612
1868
|
const dir = this.getRunDir(conversationId);
|
|
3613
|
-
const content = await
|
|
1869
|
+
const content = await readFile3(join3(dir, "USER_REQUEST.md"), "utf-8");
|
|
3614
1870
|
return content.replace(/^#\s*User request\s*\n+/, "").trim() || null;
|
|
3615
1871
|
} catch {
|
|
3616
1872
|
return null;
|
|
@@ -3619,23 +1875,23 @@ ${userInput.trim() || "(empty)"}
|
|
|
3619
1875
|
async readReadme(conversationId) {
|
|
3620
1876
|
try {
|
|
3621
1877
|
const dir = this.getRunDir(conversationId);
|
|
3622
|
-
return await
|
|
1878
|
+
return await readFile3(join3(dir, "README.md"), "utf-8");
|
|
3623
1879
|
} catch {
|
|
3624
1880
|
return null;
|
|
3625
1881
|
}
|
|
3626
1882
|
}
|
|
3627
1883
|
async saveScenarioSnapshot(conversationId, scenario) {
|
|
3628
1884
|
const dir = this.getRunDir(conversationId);
|
|
3629
|
-
await
|
|
1885
|
+
await mkdir2(dir, { recursive: true });
|
|
3630
1886
|
await atomicWriteJsonSecure(
|
|
3631
|
-
|
|
1887
|
+
join3(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE),
|
|
3632
1888
|
scenario,
|
|
3633
1889
|
true
|
|
3634
1890
|
);
|
|
3635
1891
|
}
|
|
3636
1892
|
async loadScenarioSnapshot(conversationId) {
|
|
3637
1893
|
const dir = this.getRunDir(conversationId);
|
|
3638
|
-
return readJsonFileSafe(
|
|
1894
|
+
return readJsonFileSafe(join3(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE));
|
|
3639
1895
|
}
|
|
3640
1896
|
};
|
|
3641
1897
|
var scenarioRunStore = new ScenarioRunStore();
|
|
@@ -3683,16 +1939,16 @@ async function fetchProviderModels(provider, plaintextKey) {
|
|
|
3683
1939
|
async function vaultOfProject(projectId) {
|
|
3684
1940
|
if (!projectId) return void 0;
|
|
3685
1941
|
await ensureProjectsLoaded();
|
|
3686
|
-
const
|
|
3687
|
-
if (
|
|
3688
|
-
return
|
|
1942
|
+
const refs = refsOfProjectId(projectId);
|
|
1943
|
+
if (refs.length !== 1) return void 0;
|
|
1944
|
+
return refs[0]?.orgId ?? void 0;
|
|
3689
1945
|
}
|
|
3690
1946
|
|
|
3691
1947
|
// ../server/src/services/image-storage.service.ts
|
|
3692
1948
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3693
|
-
import { mkdir as
|
|
1949
|
+
import { mkdir as mkdir3, writeFile, readFile as readFile4, unlink as unlink2, readdir as readdir3 } from "fs/promises";
|
|
3694
1950
|
import { existsSync as existsSync2 } from "fs";
|
|
3695
|
-
import { join as
|
|
1951
|
+
import { join as join4, extname, resolve, sep } from "path";
|
|
3696
1952
|
var ALLOWED_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
3697
1953
|
"image/png",
|
|
3698
1954
|
"image/jpeg",
|
|
@@ -3709,7 +1965,7 @@ var ImageStorageService = class {
|
|
|
3709
1965
|
this.dir = dir;
|
|
3710
1966
|
}
|
|
3711
1967
|
async ensureTmpDir() {
|
|
3712
|
-
await
|
|
1968
|
+
await mkdir3(this.dir, { recursive: true, mode: 448 });
|
|
3713
1969
|
}
|
|
3714
1970
|
async saveImage(buffer, originalName, mimeType, contextId) {
|
|
3715
1971
|
if (!ALLOWED_MIME_TYPES.has(mimeType)) {
|
|
@@ -3729,15 +1985,15 @@ var ImageStorageService = class {
|
|
|
3729
1985
|
const imageId = randomUUID3();
|
|
3730
1986
|
const safeExt = ext.replace(/[^a-z.]/g, "");
|
|
3731
1987
|
const fileName = `${imageId}${safeExt}`;
|
|
3732
|
-
const filePath =
|
|
1988
|
+
const filePath = join4(this.dir, fileName);
|
|
3733
1989
|
const resolvedPath = resolve(filePath);
|
|
3734
1990
|
const resolvedDir = resolve(this.dir);
|
|
3735
1991
|
if (resolvedPath !== resolvedDir && !resolvedPath.startsWith(resolvedDir + sep)) {
|
|
3736
1992
|
throw new Error("Invalid file path");
|
|
3737
1993
|
}
|
|
3738
|
-
await
|
|
1994
|
+
await writeFile(filePath, buffer, { mode: 384 });
|
|
3739
1995
|
const meta = { imageId, filePath, mimeType, originalName, createdAt: Date.now(), contextId };
|
|
3740
|
-
await
|
|
1996
|
+
await writeFile(`${filePath}.meta.json`, JSON.stringify(meta), { encoding: "utf-8", mode: 384 });
|
|
3741
1997
|
return meta;
|
|
3742
1998
|
}
|
|
3743
1999
|
/**
|
|
@@ -3755,7 +2011,7 @@ var ImageStorageService = class {
|
|
|
3755
2011
|
const files = await readdir3(this.dir);
|
|
3756
2012
|
const metaFile = files.find((f) => f.startsWith(imageId) && f.endsWith(".meta.json"));
|
|
3757
2013
|
if (!metaFile) return null;
|
|
3758
|
-
const raw = JSON.parse(await
|
|
2014
|
+
const raw = JSON.parse(await readFile4(join4(this.dir, metaFile), "utf-8"));
|
|
3759
2015
|
if (expectedContextId !== void 0 && raw.contextId !== expectedContextId) return null;
|
|
3760
2016
|
if (!existsSync2(raw.filePath)) return null;
|
|
3761
2017
|
return { filePath: raw.filePath, mimeType: raw.mimeType, contextId: raw.contextId };
|
|
@@ -3781,7 +2037,7 @@ var ImageStorageService = class {
|
|
|
3781
2037
|
await Promise.all(
|
|
3782
2038
|
files.filter((f) => f.endsWith(".meta.json")).map(async (file) => {
|
|
3783
2039
|
try {
|
|
3784
|
-
const raw = JSON.parse(await
|
|
2040
|
+
const raw = JSON.parse(await readFile4(join4(this.dir, file), "utf-8"));
|
|
3785
2041
|
if (raw.contextId === contextId) {
|
|
3786
2042
|
await unlink2(raw.filePath).catch(() => {
|
|
3787
2043
|
});
|
|
@@ -3796,7 +2052,7 @@ var ImageStorageService = class {
|
|
|
3796
2052
|
async readImageAsBase64(imageId, expectedContextId) {
|
|
3797
2053
|
const result = await this.getImagePath(imageId, expectedContextId);
|
|
3798
2054
|
if (!result) return null;
|
|
3799
|
-
const buffer = await
|
|
2055
|
+
const buffer = await readFile4(result.filePath);
|
|
3800
2056
|
return { base64: buffer.toString("base64"), mimeType: result.mimeType };
|
|
3801
2057
|
}
|
|
3802
2058
|
async listExpiredImages(ttlMs = IMAGE_TTL_MS) {
|
|
@@ -3811,7 +2067,7 @@ var ImageStorageService = class {
|
|
|
3811
2067
|
for (const file of files) {
|
|
3812
2068
|
if (!file.endsWith(".meta.json")) continue;
|
|
3813
2069
|
try {
|
|
3814
|
-
const raw = JSON.parse(await
|
|
2070
|
+
const raw = JSON.parse(await readFile4(join4(this.dir, file), "utf-8"));
|
|
3815
2071
|
if (now - raw.createdAt > ttlMs) expired.push(raw);
|
|
3816
2072
|
} catch {
|
|
3817
2073
|
}
|
|
@@ -3874,16 +2130,16 @@ async function resolveImagesToBase64(images, contextId) {
|
|
|
3874
2130
|
}
|
|
3875
2131
|
|
|
3876
2132
|
// ../server/src/trpc/routers/cloud-auth.ts
|
|
3877
|
-
import { z as
|
|
2133
|
+
import { z as z5 } from "zod";
|
|
3878
2134
|
import { hostname } from "os";
|
|
3879
2135
|
import { basename } from "path";
|
|
3880
2136
|
import { TRPCError as TRPCError3 } from "@trpc/server";
|
|
3881
2137
|
|
|
3882
2138
|
// ../server/src/services/cloud-session-store.ts
|
|
3883
|
-
import { readFile as
|
|
3884
|
-
import { dirname
|
|
2139
|
+
import { readFile as readFile5, writeFile as writeFile2, mkdir as mkdir4, chmod, unlink as unlink3 } from "fs/promises";
|
|
2140
|
+
import { dirname } from "path";
|
|
3885
2141
|
import { randomBytes as randomBytes2, createHash, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
3886
|
-
var
|
|
2142
|
+
var FILENAME = "cloud-session.json";
|
|
3887
2143
|
var STATE_TTL_MS = 5 * 60 * 1e3;
|
|
3888
2144
|
function accountIdFor(cloudUrl, userId) {
|
|
3889
2145
|
return createHash("sha256").update(`${cloudUrl.replace(/\/+$/, "")}|${userId}`).digest("hex").slice(0, 32);
|
|
@@ -3898,11 +2154,11 @@ var CloudSessionStore = class {
|
|
|
3898
2154
|
* root, but a launch directory owning its own key keeps both together.
|
|
3899
2155
|
*/
|
|
3900
2156
|
getFilePath() {
|
|
3901
|
-
return keyedFilePath(
|
|
2157
|
+
return keyedFilePath(FILENAME);
|
|
3902
2158
|
}
|
|
3903
2159
|
/** Where older versions kept it — read only, so an older Runeya keeps working. */
|
|
3904
2160
|
getLegacyFilePath() {
|
|
3905
|
-
return legacyMachineFilePath(
|
|
2161
|
+
return legacyMachineFilePath(FILENAME);
|
|
3906
2162
|
}
|
|
3907
2163
|
/**
|
|
3908
2164
|
* Read the machine file, falling back to the launch directory of old.
|
|
@@ -3912,9 +2168,9 @@ var CloudSessionStore = class {
|
|
|
3912
2168
|
*/
|
|
3913
2169
|
async readFromDisk() {
|
|
3914
2170
|
try {
|
|
3915
|
-
return await
|
|
2171
|
+
return await readFile5(await this.getFilePath(), "utf-8");
|
|
3916
2172
|
} catch {
|
|
3917
|
-
return
|
|
2173
|
+
return readFile5(this.getLegacyFilePath(), "utf-8");
|
|
3918
2174
|
}
|
|
3919
2175
|
}
|
|
3920
2176
|
/**
|
|
@@ -3988,12 +2244,12 @@ var CloudSessionStore = class {
|
|
|
3988
2244
|
}
|
|
3989
2245
|
async save() {
|
|
3990
2246
|
const filePath = await this.getFilePath();
|
|
3991
|
-
await
|
|
2247
|
+
await mkdir4(dirname(filePath), { recursive: true });
|
|
3992
2248
|
const payload = { version: 2, accounts: this.accounts };
|
|
3993
2249
|
const tmpPath = filePath + ".tmp";
|
|
3994
|
-
await
|
|
2250
|
+
await writeFile2(tmpPath, JSON.stringify(payload, null, 2), "utf-8");
|
|
3995
2251
|
await renameWithRetry(tmpPath, filePath);
|
|
3996
|
-
await
|
|
2252
|
+
await chmod(filePath, 384);
|
|
3997
2253
|
}
|
|
3998
2254
|
/**
|
|
3999
2255
|
* Ranger le token obtenu par l'échange de jeton à usage unique.
|
|
@@ -4129,25 +2385,25 @@ var cloudSessionStore = new CloudSessionStore();
|
|
|
4129
2385
|
|
|
4130
2386
|
// ../server/src/services/lan-mode.ts
|
|
4131
2387
|
import { networkInterfaces } from "os";
|
|
4132
|
-
import { readFile as
|
|
4133
|
-
import { join as
|
|
4134
|
-
import { z as
|
|
2388
|
+
import { readFile as readFile6, writeFile as writeFile3, mkdir as mkdir5 } from "fs/promises";
|
|
2389
|
+
import { join as join5 } from "path";
|
|
2390
|
+
import { z as z4 } from "zod";
|
|
4135
2391
|
var LOOPBACK2 = "127.0.0.1";
|
|
4136
2392
|
var ANY = "0.0.0.0";
|
|
4137
2393
|
var NETWORK_FILE = "network.json";
|
|
4138
|
-
var NetworkFileSchema =
|
|
4139
|
-
instances:
|
|
2394
|
+
var NetworkFileSchema = z4.object({
|
|
2395
|
+
instances: z4.record(z4.string(), z4.object({ lanEnabled: z4.boolean().default(false) })).default({})
|
|
4140
2396
|
});
|
|
4141
2397
|
var httpServer = null;
|
|
4142
2398
|
function instanceKey() {
|
|
4143
2399
|
return env.DATA_DIR;
|
|
4144
2400
|
}
|
|
4145
2401
|
function networkFilePath() {
|
|
4146
|
-
return
|
|
2402
|
+
return join5(machineRoot(), NETWORK_FILE);
|
|
4147
2403
|
}
|
|
4148
2404
|
async function readFileState() {
|
|
4149
2405
|
try {
|
|
4150
|
-
return NetworkFileSchema.parse(JSON.parse(await
|
|
2406
|
+
return NetworkFileSchema.parse(JSON.parse(await readFile6(networkFilePath(), "utf-8")));
|
|
4151
2407
|
} catch {
|
|
4152
2408
|
return { instances: {} };
|
|
4153
2409
|
}
|
|
@@ -4158,12 +2414,12 @@ async function readPersisted() {
|
|
|
4158
2414
|
}
|
|
4159
2415
|
async function writePersisted(lanEnabled) {
|
|
4160
2416
|
const root = machineRoot();
|
|
4161
|
-
await
|
|
2417
|
+
await mkdir5(root, { recursive: true });
|
|
4162
2418
|
const state = await readFileState();
|
|
4163
2419
|
state.instances[instanceKey()] = { lanEnabled };
|
|
4164
2420
|
const filePath = networkFilePath();
|
|
4165
2421
|
const tmpPath = filePath + ".tmp";
|
|
4166
|
-
await
|
|
2422
|
+
await writeFile3(tmpPath, JSON.stringify(state, null, 2), "utf-8");
|
|
4167
2423
|
await renameWithRetry(tmpPath, filePath);
|
|
4168
2424
|
}
|
|
4169
2425
|
function registerHttpServer(server) {
|
|
@@ -4261,12 +2517,12 @@ function getCloudApiUrl() {
|
|
|
4261
2517
|
return (env.CLOUD_URL ?? DEFAULT_CLOUD_API_URL).replace(/\/+$/, "");
|
|
4262
2518
|
}
|
|
4263
2519
|
async function orgCloudUrl(orgId) {
|
|
4264
|
-
const { readOrgClouds } = await import("./vault-resolver-
|
|
2520
|
+
const { readOrgClouds } = await import("./vault-resolver-E7KPKA5C.js");
|
|
4265
2521
|
const clouds = await readOrgClouds();
|
|
4266
2522
|
return (clouds[orgId] ?? getCloudApiUrl()).replace(/\/+$/, "");
|
|
4267
2523
|
}
|
|
4268
2524
|
async function accountForOrg(orgId) {
|
|
4269
|
-
const { readOrgAccounts, readOrgClouds } = await import("./vault-resolver-
|
|
2525
|
+
const { readOrgAccounts, readOrgClouds } = await import("./vault-resolver-E7KPKA5C.js");
|
|
4270
2526
|
const [stampedAccounts, stampedClouds, accounts] = await Promise.all([
|
|
4271
2527
|
readOrgAccounts(),
|
|
4272
2528
|
readOrgClouds(),
|
|
@@ -4429,15 +2685,15 @@ function isAllowedProxyPath(path) {
|
|
|
4429
2685
|
const [pathname] = path.split("?");
|
|
4430
2686
|
return PROXY_PREFIXES.some((p) => pathname === p || pathname.startsWith(p));
|
|
4431
2687
|
}
|
|
4432
|
-
var ScopeSchema =
|
|
4433
|
-
accountId:
|
|
2688
|
+
var ScopeSchema = z5.object({
|
|
2689
|
+
accountId: z5.string().min(1).max(64)
|
|
4434
2690
|
});
|
|
4435
2691
|
function scopeOf(input) {
|
|
4436
2692
|
return { accountId: input.accountId };
|
|
4437
2693
|
}
|
|
4438
2694
|
var cloudAuthRouter = router({
|
|
4439
2695
|
/** Build the cloud authorization URL to open in the browser. */
|
|
4440
|
-
startLogin: protectedProcedure.input(
|
|
2696
|
+
startLogin: protectedProcedure.input(z5.object({ returnTo: z5.string().url().max(2048) })).mutation(async ({ input }) => {
|
|
4441
2697
|
const callback = callbackForReturnTo(input.returnTo);
|
|
4442
2698
|
if (!callback) {
|
|
4443
2699
|
throw new TRPCError3({
|
|
@@ -4475,7 +2731,7 @@ var cloudAuthRouter = router({
|
|
|
4475
2731
|
* `null` quand rien ne rattache cette organisation à un compte de ce poste :
|
|
4476
2732
|
* l'interface l'affiche comme déconnectée, ce qu'elle est.
|
|
4477
2733
|
*/
|
|
4478
|
-
accountForOrg: protectedProcedure.input(
|
|
2734
|
+
accountForOrg: protectedProcedure.input(z5.object({ orgId: z5.string().min(1).max(128) })).query(async ({ input }) => ({ accountId: await accountForOrg(input.orgId) })),
|
|
4479
2735
|
/**
|
|
4480
2736
|
* Les comptes liés à ce poste, et l'organisation dont chacun répond.
|
|
4481
2737
|
*
|
|
@@ -4483,7 +2739,7 @@ var cloudAuthRouter = router({
|
|
|
4483
2739
|
* du projet ouvert, sous quel compte il se synchronise.
|
|
4484
2740
|
*/
|
|
4485
2741
|
accounts: protectedProcedure.query(async () => {
|
|
4486
|
-
const { readOrgAccounts } = await import("./vault-resolver-
|
|
2742
|
+
const { readOrgAccounts } = await import("./vault-resolver-E7KPKA5C.js");
|
|
4487
2743
|
return { accounts: await cloudSessionStore.list(), orgAccounts: await readOrgAccounts() };
|
|
4488
2744
|
}),
|
|
4489
2745
|
/** The cloud session (user + org), fetched with the stored token. */
|
|
@@ -4501,7 +2757,7 @@ var cloudAuthRouter = router({
|
|
|
4501
2757
|
const identity = await readCloudIdentity(account.cloudUrl, await cloudSessionStore.getToken(accountId) ?? "");
|
|
4502
2758
|
if (identity) {
|
|
4503
2759
|
const settled = await cloudSessionStore.describe(accountId, identity);
|
|
4504
|
-
const { renameOrgAccount } = await import("./vault-resolver-
|
|
2760
|
+
const { renameOrgAccount } = await import("./vault-resolver-E7KPKA5C.js");
|
|
4505
2761
|
await renameOrgAccount(accountId, settled);
|
|
4506
2762
|
}
|
|
4507
2763
|
}
|
|
@@ -4531,7 +2787,7 @@ var cloudAuthRouter = router({
|
|
|
4531
2787
|
* drops the session, and the local token is dropped here too, so nothing is
|
|
4532
2788
|
* left holding a dead credential.
|
|
4533
2789
|
*/
|
|
4534
|
-
revokeDevice: protectedProcedure.input(ScopeSchema.extend({ id:
|
|
2790
|
+
revokeDevice: protectedProcedure.input(ScopeSchema.extend({ id: z5.string().min(1).max(128) })).mutation(async ({ input }) => {
|
|
4535
2791
|
const { accountId } = input;
|
|
4536
2792
|
if (!accountId) throw new TRPCError3({ code: "PRECONDITION_FAILED", message: "No cloud account for this scope." });
|
|
4537
2793
|
const res = await cloudFetch2("/api/auth/device/revoke", {
|
|
@@ -4554,17 +2810,17 @@ var cloudAuthRouter = router({
|
|
|
4554
2810
|
* the cloud: the web UI asks its own server, which holds the credential.
|
|
4555
2811
|
*/
|
|
4556
2812
|
proxy: protectedProcedure.input(
|
|
4557
|
-
|
|
4558
|
-
path:
|
|
4559
|
-
method:
|
|
4560
|
-
query:
|
|
4561
|
-
body:
|
|
2813
|
+
z5.object({
|
|
2814
|
+
path: z5.string().min(1).max(512),
|
|
2815
|
+
method: z5.enum(["GET", "POST"]).default("GET"),
|
|
2816
|
+
query: z5.record(z5.string().max(128), z5.string().max(2048)).optional(),
|
|
2817
|
+
body: z5.unknown().optional(),
|
|
4562
2818
|
// Obligatoire, et seul : c'est la porte par laquelle passent tous les
|
|
4563
2819
|
// appels cloud de l'interface. La laisser optionnelle est ce qui a
|
|
4564
2820
|
// permis aux composables partagées de n'envoyer aucune portée pendant
|
|
4565
2821
|
// trente routes, et au serveur de retomber sur « le compte unique du
|
|
4566
2822
|
// poste » — muet dès qu'il y en a eu deux.
|
|
4567
|
-
accountId:
|
|
2823
|
+
accountId: z5.string().min(1).max(64)
|
|
4568
2824
|
})
|
|
4569
2825
|
).mutation(async ({ input }) => {
|
|
4570
2826
|
if (!isAllowedProxyPath(input.path)) {
|
|
@@ -5259,6 +3515,12 @@ function generateKanbanPopulatePrompt(board, scope) {
|
|
|
5259
3515
|
lines.push("");
|
|
5260
3516
|
return lines.join("\n");
|
|
5261
3517
|
}
|
|
3518
|
+
async function kanbanContextFor(conv, scope) {
|
|
3519
|
+
if (!conv?.kanbanBoardId) return "";
|
|
3520
|
+
const kanbanBoard = await kanbanStore.get(conv.kanbanBoardId);
|
|
3521
|
+
if (!kanbanBoard) return "";
|
|
3522
|
+
return "\n\n" + (conv.kanbanCardId ? await buildKanbanCardPrompt(kanbanBoard, conv.kanbanCardId, scope) : generateKanbanPopulatePrompt(kanbanBoard, scope));
|
|
3523
|
+
}
|
|
5262
3524
|
|
|
5263
3525
|
// ../server/src/services/ai-runners/claude-code-runner.ts
|
|
5264
3526
|
import { spawn } from "child_process";
|
|
@@ -5431,7 +3693,7 @@ function tailTranscript(opts) {
|
|
|
5431
3693
|
|
|
5432
3694
|
// ../server/src/services/ai-runners/claude-pty-runner.ts
|
|
5433
3695
|
async function getAgentManager() {
|
|
5434
|
-
const { agentManager: agentManager2 } = await import("./agent-manager-
|
|
3696
|
+
const { agentManager: agentManager2 } = await import("./agent-manager-46C4NIQ2.js");
|
|
5435
3697
|
return agentManager2;
|
|
5436
3698
|
}
|
|
5437
3699
|
function buildPtyArgs(params, sessionId, opts) {
|
|
@@ -5449,7 +3711,7 @@ ${TOKEN_INSTRUCTIONS}`;
|
|
|
5449
3711
|
"--append-system-prompt",
|
|
5450
3712
|
systemPrompt,
|
|
5451
3713
|
...mcpArgs,
|
|
5452
|
-
prompt
|
|
3714
|
+
...opts.deferPrompt ? [] : [prompt]
|
|
5453
3715
|
];
|
|
5454
3716
|
return args;
|
|
5455
3717
|
}
|
|
@@ -5498,7 +3760,9 @@ async function runClaudeViaPty(params) {
|
|
|
5498
3760
|
const { agentId, signal, skipTokenInstructions } = params;
|
|
5499
3761
|
if (!agentId) throw new Error("runClaudeViaPty requires an agentId");
|
|
5500
3762
|
const sessionId = params.sessionId && params.sessionId.length > 0 ? params.sessionId : randomUUID4();
|
|
5501
|
-
const
|
|
3763
|
+
const deferPrompt = !!params.mcpConfig;
|
|
3764
|
+
const mcpReadyId = deferPrompt ? randomUUID4() : void 0;
|
|
3765
|
+
const args = buildPtyArgs(params, sessionId, { excludeMcp: true, deferPrompt });
|
|
5502
3766
|
const agentMgr = await getAgentManager();
|
|
5503
3767
|
const displayArgs = args.map((a) => {
|
|
5504
3768
|
if (a === params.prompt) return "<prompt>";
|
|
@@ -5578,7 +3842,7 @@ async function runClaudeViaPty(params) {
|
|
|
5578
3842
|
}
|
|
5579
3843
|
}
|
|
5580
3844
|
});
|
|
5581
|
-
agentMgr.agentMutation(agentId, "ai.spawnPty", {
|
|
3845
|
+
const spawned = agentMgr.agentMutation(agentId, "ai.spawnPty", {
|
|
5582
3846
|
cli: "claude",
|
|
5583
3847
|
args,
|
|
5584
3848
|
env: params.injectedEnv && Object.keys(params.injectedEnv).length > 0 ? params.injectedEnv : void 0,
|
|
@@ -5598,7 +3862,9 @@ async function runClaudeViaPty(params) {
|
|
|
5598
3862
|
// signe elle-même, faute de requête HTTP d'où le tirer.
|
|
5599
3863
|
mcpApiToken: params.apiToken || void 0,
|
|
5600
3864
|
mcpProjectId: params.projectId || void 0,
|
|
5601
|
-
mcpOrgId: params.orgId || void 0
|
|
3865
|
+
mcpOrgId: params.orgId || void 0,
|
|
3866
|
+
// Un agent ancien ignore ce champ : le plafond de `waitForPtyInput` couvre ce cas.
|
|
3867
|
+
mcpReadyId
|
|
5602
3868
|
}).then(({ spawnId: id }) => {
|
|
5603
3869
|
spawnId = id;
|
|
5604
3870
|
if (signal.aborted) {
|
|
@@ -5608,6 +3874,18 @@ async function runClaudeViaPty(params) {
|
|
|
5608
3874
|
}).catch((err) => {
|
|
5609
3875
|
fail(new Error(`Failed to spawn claude PTY: ${err.message}`));
|
|
5610
3876
|
});
|
|
3877
|
+
if (mcpReadyId) {
|
|
3878
|
+
void spawned.then(async () => {
|
|
3879
|
+
if (settled || !spawnId) return;
|
|
3880
|
+
const id = spawnId;
|
|
3881
|
+
const wait = await waitForPtyInput({ agentId, spawnId: id, mcpReadyId, runner: "claude-code", signal: innerAbort.signal });
|
|
3882
|
+
if (settled || wait.status !== "ready") return;
|
|
3883
|
+
if (!wait.mcpReady) console.warn(`[claude-pty] serveur MCP non signal\xE9 apr\xE8s ${PTY_MCP_READY_MAX_MS} ms, prompt \xE9crit quand m\xEAme (session ${sessionId})`);
|
|
3884
|
+
await pasteToPty((data) => agentMgr.agentMutation(agentId, "ai.writePty", { spawnId: id, data }), params.prompt, { lead: PASTED_TASK_LEAD });
|
|
3885
|
+
}).catch((err) => {
|
|
3886
|
+
fail(new Error(`Failed to write the prompt to the claude PTY: ${err.message}`));
|
|
3887
|
+
});
|
|
3888
|
+
}
|
|
5611
3889
|
signal.addEventListener("abort", () => fail(new Error("Aborted")), { once: true });
|
|
5612
3890
|
void tail.whenDone.then(() => {
|
|
5613
3891
|
if (settled) return;
|
|
@@ -5931,18 +4209,6 @@ var ClaudeCodeRunner = class {
|
|
|
5931
4209
|
import { spawn as spawn2 } from "child_process";
|
|
5932
4210
|
import { createInterface } from "readline";
|
|
5933
4211
|
import { EventEmitter } from "events";
|
|
5934
|
-
function resolveMcpArgs(env2) {
|
|
5935
|
-
if (!env2["RUNEYA_API_TOKEN"]) return [];
|
|
5936
|
-
try {
|
|
5937
|
-
return codexMcpArgs({
|
|
5938
|
-
RUNEYA_BASE_URL: env2["RUNEYA_BASE_URL"],
|
|
5939
|
-
RUNEYA_API_TOKEN: env2["RUNEYA_API_TOKEN"]
|
|
5940
|
-
});
|
|
5941
|
-
} catch (err) {
|
|
5942
|
-
console.warn("[codex-app-server] Failed to resolve MCP server path:", err.message);
|
|
5943
|
-
return [];
|
|
5944
|
-
}
|
|
5945
|
-
}
|
|
5946
4212
|
var CodexAppServerClient = class extends EventEmitter {
|
|
5947
4213
|
proc = null;
|
|
5948
4214
|
rl = null;
|
|
@@ -5964,7 +4230,10 @@ var CodexAppServerClient = class extends EventEmitter {
|
|
|
5964
4230
|
*/
|
|
5965
4231
|
async connect(runeyaEnv) {
|
|
5966
4232
|
const env2 = { ...process.env, ...runeyaEnv };
|
|
5967
|
-
const invocation = resolveCliInvocation("codex", [
|
|
4233
|
+
const invocation = resolveCliInvocation("codex", [
|
|
4234
|
+
...codexRuneyaMcpArgs({ RUNEYA_BASE_URL: env2["RUNEYA_BASE_URL"], RUNEYA_API_TOKEN: env2["RUNEYA_API_TOKEN"] }),
|
|
4235
|
+
"app-server"
|
|
4236
|
+
]);
|
|
5968
4237
|
this.proc = spawn2(invocation.command, invocation.args, {
|
|
5969
4238
|
windowsHide: true,
|
|
5970
4239
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -6125,7 +4394,7 @@ async function shutdownCodexAppServer() {
|
|
|
6125
4394
|
}
|
|
6126
4395
|
}
|
|
6127
4396
|
async function getAgentManager2() {
|
|
6128
|
-
const { agentManager: agentManager2 } = await import("./agent-manager-
|
|
4397
|
+
const { agentManager: agentManager2 } = await import("./agent-manager-46C4NIQ2.js");
|
|
6129
4398
|
return agentManager2;
|
|
6130
4399
|
}
|
|
6131
4400
|
function parseRuneyaCurlCommand(command) {
|
|
@@ -6609,19 +4878,19 @@ function toConversationSummary(conv) {
|
|
|
6609
4878
|
const { messages, ...rest } = conv;
|
|
6610
4879
|
return { ...rest, messages: [], messagesOmitted: true, messageCount: messages.length };
|
|
6611
4880
|
}
|
|
6612
|
-
var MessageSchema =
|
|
6613
|
-
role:
|
|
6614
|
-
content:
|
|
4881
|
+
var MessageSchema = z6.object({
|
|
4882
|
+
role: z6.enum(["user", "assistant"]),
|
|
4883
|
+
content: z6.string().max(1e5)
|
|
6615
4884
|
});
|
|
6616
|
-
var ImagePartSchema =
|
|
6617
|
-
type:
|
|
6618
|
-
imageId:
|
|
6619
|
-
url:
|
|
6620
|
-
mimeType:
|
|
6621
|
-
altText:
|
|
4885
|
+
var ImagePartSchema = z6.object({
|
|
4886
|
+
type: z6.literal("image"),
|
|
4887
|
+
imageId: z6.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i),
|
|
4888
|
+
url: z6.string().max(500),
|
|
4889
|
+
mimeType: z6.string().max(100),
|
|
4890
|
+
altText: z6.string().max(500).optional()
|
|
6622
4891
|
});
|
|
6623
|
-
var ImagePartsSchema =
|
|
6624
|
-
var SectionNameSchema =
|
|
4892
|
+
var ImagePartsSchema = z6.array(ImagePartSchema).max(5).optional();
|
|
4893
|
+
var SectionNameSchema = z6.string().trim().min(1).max(80);
|
|
6625
4894
|
var LANGUAGE_INSTRUCTION = "Always respond in the same language the user writes in.";
|
|
6626
4895
|
async function buildSystemPrompt(apiKey) {
|
|
6627
4896
|
const allServices = await serviceStore.list();
|
|
@@ -6853,12 +5122,6 @@ async function* drainSession(session, signal, isReconnect) {
|
|
|
6853
5122
|
if (signal?.aborted) return;
|
|
6854
5123
|
}
|
|
6855
5124
|
}
|
|
6856
|
-
async function kanbanContextFor(conv, scope) {
|
|
6857
|
-
if (!conv?.kanbanBoardId) return "";
|
|
6858
|
-
const kanbanBoard = await kanbanStore.get(conv.kanbanBoardId);
|
|
6859
|
-
if (!kanbanBoard) return "";
|
|
6860
|
-
return "\n\n" + (conv.kanbanCardId ? await buildKanbanCardPrompt(kanbanBoard, conv.kanbanCardId, scope) : generateKanbanPopulatePrompt(kanbanBoard, scope));
|
|
6861
|
-
}
|
|
6862
5125
|
async function cliToken(ctx) {
|
|
6863
5126
|
const bearer = ctx.authorization?.startsWith("Bearer ") ? ctx.authorization.slice(7) : void 0;
|
|
6864
5127
|
if (!bearer || ctx.callerType !== "app") return bearer;
|
|
@@ -7018,24 +5281,24 @@ async function startCodexSession(convId, prompt, opts = {}) {
|
|
|
7018
5281
|
});
|
|
7019
5282
|
return { session, done };
|
|
7020
5283
|
}
|
|
7021
|
-
var MessagePartSchema =
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
type:
|
|
5284
|
+
var MessagePartSchema = z6.discriminatedUnion("type", [
|
|
5285
|
+
z6.object({ type: z6.literal("thinking"), text: z6.string().max(1e5) }),
|
|
5286
|
+
z6.object({ type: z6.literal("text"), text: z6.string().max(1e5) }),
|
|
5287
|
+
z6.object({
|
|
5288
|
+
type: z6.literal("tool"),
|
|
7026
5289
|
id: Id128Schema,
|
|
7027
5290
|
name: Id128Schema,
|
|
7028
|
-
input:
|
|
7029
|
-
rawInput:
|
|
7030
|
-
result:
|
|
7031
|
-
isError:
|
|
7032
|
-
pending:
|
|
5291
|
+
input: z6.record(z6.string(), z6.unknown()).optional(),
|
|
5292
|
+
rawInput: z6.string().max(1e4).optional(),
|
|
5293
|
+
result: z6.unknown().optional(),
|
|
5294
|
+
isError: z6.boolean().optional(),
|
|
5295
|
+
pending: z6.boolean()
|
|
7033
5296
|
})
|
|
7034
5297
|
]);
|
|
7035
|
-
var StoredMessageSchema =
|
|
7036
|
-
role:
|
|
7037
|
-
content:
|
|
7038
|
-
parts:
|
|
5298
|
+
var StoredMessageSchema = z6.object({
|
|
5299
|
+
role: z6.enum(["user", "assistant"]),
|
|
5300
|
+
content: z6.string().max(1e5),
|
|
5301
|
+
parts: z6.array(MessagePartSchema).max(200).optional()
|
|
7039
5302
|
});
|
|
7040
5303
|
async function conversationCwd(conv) {
|
|
7041
5304
|
return resolveConversationCwd(conv.id);
|
|
@@ -7043,6 +5306,26 @@ async function conversationCwd(conv) {
|
|
|
7043
5306
|
function startCliSession(runner, convId, prompt, opts = {}) {
|
|
7044
5307
|
return runner === "codex" ? startCodexSession(convId, prompt, opts) : startClaudeCodeSession(convId, prompt, opts);
|
|
7045
5308
|
}
|
|
5309
|
+
async function writeWhenPtyReady(opts) {
|
|
5310
|
+
const { agentId, spawnId } = opts;
|
|
5311
|
+
const write = (data) => agentManager.agentMutation(agentId, "ai.writePty", { spawnId, data });
|
|
5312
|
+
try {
|
|
5313
|
+
const wait = await waitForPtyInput({ agentId, spawnId, mcpReadyId: opts.mcpReadyId, runner: opts.runner });
|
|
5314
|
+
if (wait.status !== "ready") return;
|
|
5315
|
+
if (opts.contexts.length > 0) {
|
|
5316
|
+
const body = opts.contexts.map((c) => `### ${c.label}
|
|
5317
|
+
${c.text}`).join("\n\n");
|
|
5318
|
+
await write(`\x1B[200~---------
|
|
5319
|
+
Context:
|
|
5320
|
+
\x1B[201~\x1B[200~${body}\x1B[201~\x1B[200~
|
|
5321
|
+
---------
|
|
5322
|
+
\x1B[201~`);
|
|
5323
|
+
}
|
|
5324
|
+
if (opts.initialPrompt) await pasteToPty(write, opts.initialPrompt, { lead: PASTED_TASK_LEAD });
|
|
5325
|
+
} catch (err) {
|
|
5326
|
+
console.warn("[chat.pty.start] deferred writePty failed:", err.message);
|
|
5327
|
+
}
|
|
5328
|
+
}
|
|
7046
5329
|
var chatRouter = router({
|
|
7047
5330
|
conversations: router({
|
|
7048
5331
|
/**
|
|
@@ -7052,7 +5335,7 @@ var chatRouter = router({
|
|
|
7052
5335
|
* et sans le même filtre il y reversait toutes les conversations de la
|
|
7053
5336
|
* machine, une seconde après un chargement pourtant filtré.
|
|
7054
5337
|
*/
|
|
7055
|
-
onChanged: protectedProcedure.input(
|
|
5338
|
+
onChanged: protectedProcedure.input(z6.object({
|
|
7056
5339
|
projectId: Id128Schema.optional(),
|
|
7057
5340
|
orgId: Id128Schema.nullable().optional()
|
|
7058
5341
|
}).optional()).subscription(async function* ({ input }) {
|
|
@@ -7104,7 +5387,7 @@ var chatRouter = router({
|
|
|
7104
5387
|
* reachable where they were written, without leaking into every other
|
|
7105
5388
|
* organization's projects.
|
|
7106
5389
|
*/
|
|
7107
|
-
list: protectedProcedure.input(
|
|
5390
|
+
list: protectedProcedure.input(z6.object({
|
|
7108
5391
|
projectId: Id128Schema.optional(),
|
|
7109
5392
|
orgId: Id128Schema.nullable().optional()
|
|
7110
5393
|
}).optional()).query(async ({ input }) => {
|
|
@@ -7112,24 +5395,24 @@ var chatRouter = router({
|
|
|
7112
5395
|
const convs = projectId ? await conversationStore.listForProject({ id: projectId, orgId: input?.orgId }) : await conversationStore.list();
|
|
7113
5396
|
return convs.map(toConversationSummary);
|
|
7114
5397
|
}),
|
|
7115
|
-
get: protectedProcedure.input(
|
|
5398
|
+
get: protectedProcedure.input(z6.object({ id: Id128Schema })).query(async ({ input }) => {
|
|
7116
5399
|
const conv = await conversationStore.get(input.id);
|
|
7117
5400
|
if (!conv) throw new TRPCError4({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
7118
5401
|
return conv;
|
|
7119
5402
|
}),
|
|
7120
|
-
create: protectedProcedure.input(
|
|
5403
|
+
create: protectedProcedure.input(z6.object({
|
|
7121
5404
|
title: ShortString200Schema.optional(),
|
|
7122
|
-
isCli:
|
|
7123
|
-
runner:
|
|
5405
|
+
isCli: z6.boolean().optional(),
|
|
5406
|
+
runner: z6.enum(["claude-code", "codex"]).optional(),
|
|
7124
5407
|
providerId: Id128Schema.optional(),
|
|
7125
5408
|
scenarioId: Id128Schema.optional(),
|
|
7126
|
-
scenarioInput:
|
|
5409
|
+
scenarioInput: z6.string().max(1e5).optional(),
|
|
7127
5410
|
kanbanBoardId: Id128Schema.optional(),
|
|
7128
5411
|
kanbanCardId: Id128Schema.optional(),
|
|
7129
5412
|
// The project the conversation is about: only the caller knows which
|
|
7130
5413
|
// one it is looking at.
|
|
7131
5414
|
projectId: Id128Schema.optional(),
|
|
7132
|
-
orgId:
|
|
5415
|
+
orgId: z6.string().max(128).nullable().optional(),
|
|
7133
5416
|
// L'agent où la conversation travaille. Absent = agent par défaut.
|
|
7134
5417
|
agentId: Id128Schema.optional()
|
|
7135
5418
|
})).mutation(async ({ input }) => {
|
|
@@ -7172,13 +5455,13 @@ var chatRouter = router({
|
|
|
7172
5455
|
}
|
|
7173
5456
|
return conv;
|
|
7174
5457
|
}),
|
|
7175
|
-
update: protectedProcedure.input(
|
|
5458
|
+
update: protectedProcedure.input(z6.object({
|
|
7176
5459
|
id: Id128Schema,
|
|
7177
5460
|
title: ShortString200Schema.optional(),
|
|
7178
|
-
messages:
|
|
7179
|
-
contextUsage:
|
|
7180
|
-
inputTokens:
|
|
7181
|
-
contextWindow:
|
|
5461
|
+
messages: z6.array(StoredMessageSchema).max(500).optional(),
|
|
5462
|
+
contextUsage: z6.object({
|
|
5463
|
+
inputTokens: z6.number().int().nonnegative(),
|
|
5464
|
+
contextWindow: z6.number().int().positive()
|
|
7182
5465
|
}).optional()
|
|
7183
5466
|
})).mutation(async ({ input }) => {
|
|
7184
5467
|
const conv = await conversationStore.update(input.id, { title: input.title, messages: input.messages, contextUsage: input.contextUsage });
|
|
@@ -7192,14 +5475,14 @@ var chatRouter = router({
|
|
|
7192
5475
|
* messages, et un réglage qui voyage avec 500 messages se perdrait dans un
|
|
7193
5476
|
* patch dont ce n'est pas le sujet.
|
|
7194
5477
|
*/
|
|
7195
|
-
setCompanion: protectedProcedure.input(
|
|
5478
|
+
setCompanion: protectedProcedure.input(z6.object({
|
|
7196
5479
|
id: Id128Schema,
|
|
7197
|
-
enabled:
|
|
7198
|
-
runner:
|
|
5480
|
+
enabled: z6.boolean(),
|
|
5481
|
+
runner: z6.enum(["claude-code", "codex"]).optional(),
|
|
7199
5482
|
/** Chaîne vide = modèle par défaut du runner. */
|
|
7200
|
-
model:
|
|
5483
|
+
model: z6.string().max(200).optional(),
|
|
7201
5484
|
/** Chaîne vide = effort par défaut. */
|
|
7202
|
-
effort:
|
|
5485
|
+
effort: z6.enum(["low", "normal", "high", ""]).optional()
|
|
7203
5486
|
})).mutation(async ({ input }) => {
|
|
7204
5487
|
const existing = await conversationStore.get(input.id);
|
|
7205
5488
|
if (!existing) throw new TRPCError4({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
@@ -7225,7 +5508,7 @@ var chatRouter = router({
|
|
|
7225
5508
|
* compagnon silencieux parce qu'il n'avait rien à dire et un message qui
|
|
7226
5509
|
* n'a pas pu être transmis se ressemblent parfaitement sans cette trace.
|
|
7227
5510
|
*/
|
|
7228
|
-
companionDelivery: protectedProcedure.input(
|
|
5511
|
+
companionDelivery: protectedProcedure.input(z6.object({ id: Id128Schema })).query(async ({ input }) => {
|
|
7229
5512
|
const conv = await conversationStore.get(input.id);
|
|
7230
5513
|
const companionId = conv?.companionConversationId;
|
|
7231
5514
|
const running = isCompanionThinking(input.id) || (companionId ? activeSessionRegistry.get(companionId)?.status === "running" : false);
|
|
@@ -7275,7 +5558,7 @@ var chatRouter = router({
|
|
|
7275
5558
|
* route emprunte exactement le chemin de livraison, sans le jugement, et
|
|
7276
5559
|
* rend PAR OÙ le message est passé.
|
|
7277
5560
|
*/
|
|
7278
|
-
testCompanionBridge: protectedProcedure.input(
|
|
5561
|
+
testCompanionBridge: protectedProcedure.input(z6.object({ id: Id128Schema })).mutation(async ({ input }) => {
|
|
7279
5562
|
const conv = await conversationStore.get(input.id);
|
|
7280
5563
|
if (!conv) throw new TRPCError4({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
7281
5564
|
const route = await deliverToMainConversation(
|
|
@@ -7296,7 +5579,7 @@ var chatRouter = router({
|
|
|
7296
5579
|
* entier, bien plus qu'une requête ne doit rester ouverte. L'interface suit
|
|
7297
5580
|
* l'avancement par l'état poussé « il relit », déjà en place.
|
|
7298
5581
|
*/
|
|
7299
|
-
runCompanionNow: protectedProcedure.input(
|
|
5582
|
+
runCompanionNow: protectedProcedure.input(z6.object({ id: Id128Schema })).mutation(async ({ input }) => {
|
|
7300
5583
|
const conv = await conversationStore.get(input.id);
|
|
7301
5584
|
if (!conv) throw new TRPCError4({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
7302
5585
|
if (conv.isCompanion) {
|
|
@@ -7313,7 +5596,7 @@ var chatRouter = router({
|
|
|
7313
5596
|
});
|
|
7314
5597
|
return { started: true };
|
|
7315
5598
|
}),
|
|
7316
|
-
delete: protectedProcedure.input(
|
|
5599
|
+
delete: protectedProcedure.input(z6.object({ id: Id128Schema })).mutation(async ({ input }) => {
|
|
7317
5600
|
const existing = await conversationStore.get(input.id);
|
|
7318
5601
|
if (!existing) throw new TRPCError4({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
7319
5602
|
resetRelaunches(input.id);
|
|
@@ -7341,12 +5624,12 @@ var chatRouter = router({
|
|
|
7341
5624
|
(err) => console.error("[chat.delete] Image cleanup error:", err)
|
|
7342
5625
|
);
|
|
7343
5626
|
}),
|
|
7344
|
-
archive: protectedProcedure.input(
|
|
5627
|
+
archive: protectedProcedure.input(z6.object({ id: Id128Schema, archived: z6.boolean() })).mutation(async ({ input }) => {
|
|
7345
5628
|
const conv = await conversationStore.update(input.id, { archived: input.archived });
|
|
7346
5629
|
if (!conv) throw new TRPCError4({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
7347
5630
|
return conv;
|
|
7348
5631
|
}),
|
|
7349
|
-
pin: protectedProcedure.input(
|
|
5632
|
+
pin: protectedProcedure.input(z6.object({ id: Id128Schema, pinned: z6.boolean() })).mutation(async ({ input }) => {
|
|
7350
5633
|
const conv = await conversationStore.setPinned(input.id, input.pinned);
|
|
7351
5634
|
if (!conv) throw new TRPCError4({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
7352
5635
|
return conv;
|
|
@@ -7354,16 +5637,16 @@ var chatRouter = router({
|
|
|
7354
5637
|
// L'ordre ne vaut que pour les ids donnés : l'écran ne connaît que les
|
|
7355
5638
|
// conversations du projet courant, et les épinglées des autres projets
|
|
7356
5639
|
// n'ont pas à bouger parce qu'on a rangé celles-ci.
|
|
7357
|
-
reorderPinned: protectedProcedure.input(
|
|
5640
|
+
reorderPinned: protectedProcedure.input(z6.object({ ids: z6.array(Id128Schema).max(500) })).mutation(async ({ input }) => {
|
|
7358
5641
|
await conversationStore.reorderPinned(input.ids);
|
|
7359
5642
|
}),
|
|
7360
5643
|
// Ranger des conversations dans une section du projet, ou les en sortir
|
|
7361
5644
|
// (`sectionId: null`). Une conversation d'un autre projet est refusée :
|
|
7362
5645
|
// la section ne se verrait pas là où la conversation s'affiche.
|
|
7363
|
-
setSection: protectedProcedure.input(
|
|
5646
|
+
setSection: protectedProcedure.input(z6.object({
|
|
7364
5647
|
projectId: Id128Schema,
|
|
7365
5648
|
sectionId: Id128Schema.nullable(),
|
|
7366
|
-
ids:
|
|
5649
|
+
ids: z6.array(Id128Schema).max(500)
|
|
7367
5650
|
})).mutation(async ({ input }) => {
|
|
7368
5651
|
if (input.sectionId !== null && !await conversationSectionStore.get(input.projectId, input.sectionId)) {
|
|
7369
5652
|
throw new TRPCError4({ code: "NOT_FOUND", message: "Section not found" });
|
|
@@ -7379,28 +5662,28 @@ var chatRouter = router({
|
|
|
7379
5662
|
}),
|
|
7380
5663
|
// Les sections de la barre latérale, propres à chaque projet.
|
|
7381
5664
|
sections: router({
|
|
7382
|
-
list: protectedProcedure.input(
|
|
7383
|
-
create: protectedProcedure.input(
|
|
7384
|
-
rename: protectedProcedure.input(
|
|
5665
|
+
list: protectedProcedure.input(z6.object({ projectId: Id128Schema })).query(({ input }) => conversationSectionStore.list(input.projectId)),
|
|
5666
|
+
create: protectedProcedure.input(z6.object({ projectId: Id128Schema, name: SectionNameSchema })).mutation(({ input }) => conversationSectionStore.create(input.projectId, input.name)),
|
|
5667
|
+
rename: protectedProcedure.input(z6.object({ projectId: Id128Schema, id: Id128Schema, name: SectionNameSchema })).mutation(async ({ input }) => {
|
|
7385
5668
|
const section = await conversationSectionStore.rename(input.projectId, input.id, input.name);
|
|
7386
5669
|
if (!section) throw new TRPCError4({ code: "NOT_FOUND", message: "Section not found" });
|
|
7387
5670
|
return section;
|
|
7388
5671
|
}),
|
|
7389
5672
|
// Les conversations de la section retombent dans la liste, rien n'est supprimé.
|
|
7390
|
-
delete: protectedProcedure.input(
|
|
5673
|
+
delete: protectedProcedure.input(z6.object({ projectId: Id128Schema, id: Id128Schema })).mutation(async ({ input }) => {
|
|
7391
5674
|
if (!await conversationSectionStore.remove(input.projectId, input.id)) {
|
|
7392
5675
|
throw new TRPCError4({ code: "NOT_FOUND", message: "Section not found" });
|
|
7393
5676
|
}
|
|
7394
5677
|
await conversationStore.clearSection(input.id);
|
|
7395
5678
|
}),
|
|
7396
|
-
reorder: protectedProcedure.input(
|
|
5679
|
+
reorder: protectedProcedure.input(z6.object({ projectId: Id128Schema, ids: z6.array(Id128Schema).max(200) })).mutation(({ input }) => conversationSectionStore.reorder(input.projectId, input.ids))
|
|
7397
5680
|
}),
|
|
7398
|
-
listModels: protectedProcedure.input(
|
|
5681
|
+
listModels: protectedProcedure.input(z6.object({
|
|
7399
5682
|
providerId: Id128Schema.optional(),
|
|
7400
5683
|
// Le projet dit de quelle organisation lire les fournisseurs ; absent,
|
|
7401
5684
|
// ce sont ceux du poste.
|
|
7402
5685
|
projectId: Id128Schema.optional(),
|
|
7403
|
-
orgId:
|
|
5686
|
+
orgId: z6.string().max(128).nullable().optional()
|
|
7404
5687
|
}).optional()).query(async ({ input }) => {
|
|
7405
5688
|
const active = await getActiveProvider(
|
|
7406
5689
|
input?.providerId,
|
|
@@ -7418,11 +5701,11 @@ var chatRouter = router({
|
|
|
7418
5701
|
* `model/list` de l'app-server pour Codex, sur l'agent visé (celui par
|
|
7419
5702
|
* défaut sinon). Liste vide si Codex ne répond pas.
|
|
7420
5703
|
*/
|
|
7421
|
-
listCliModels: protectedProcedure.input(
|
|
7422
|
-
runner:
|
|
5704
|
+
listCliModels: protectedProcedure.input(z6.object({
|
|
5705
|
+
runner: z6.enum(["claude-code", "codex"]),
|
|
7423
5706
|
agentId: Id128Schema.optional()
|
|
7424
5707
|
})).query(async ({ input }) => ({ models: await listCliModels(input.runner, input.agentId) })),
|
|
7425
|
-
checkCliAvailability: protectedProcedure.input(
|
|
5708
|
+
checkCliAvailability: protectedProcedure.input(z6.object({ agentId: Id128Schema.optional() }).optional()).query(async ({ input }) => {
|
|
7426
5709
|
if (input?.agentId) {
|
|
7427
5710
|
try {
|
|
7428
5711
|
const res = await agentManager.agentQuery(
|
|
@@ -7440,12 +5723,12 @@ var chatRouter = router({
|
|
|
7440
5723
|
]);
|
|
7441
5724
|
return { available: claudeOk, codexAvailable: codexOk };
|
|
7442
5725
|
}),
|
|
7443
|
-
enhanceScenarioPrompt: protectedProcedure.input(
|
|
7444
|
-
text:
|
|
5726
|
+
enhanceScenarioPrompt: protectedProcedure.input(z6.object({
|
|
5727
|
+
text: z6.string().max(1e4),
|
|
7445
5728
|
providerId: Id128Schema.optional(),
|
|
7446
|
-
model:
|
|
5729
|
+
model: z6.string().max(200).optional(),
|
|
7447
5730
|
projectId: Id128Schema.optional(),
|
|
7448
|
-
orgId:
|
|
5731
|
+
orgId: z6.string().max(128).nullable().optional()
|
|
7449
5732
|
})).mutation(async ({ input }) => {
|
|
7450
5733
|
const active = await getActiveProvider(
|
|
7451
5734
|
input.providerId,
|
|
@@ -7488,22 +5771,22 @@ Return ONLY the improved text, no explanation, no preamble.`;
|
|
|
7488
5771
|
}),
|
|
7489
5772
|
listClaudeCodeModels: protectedProcedure.query(() => ["opus", "sonnet", "haiku"]),
|
|
7490
5773
|
getStreamingStatus: protectedProcedure.query(() => activeSessionRegistry.listActive()),
|
|
7491
|
-
stopSession: protectedProcedure.input(
|
|
5774
|
+
stopSession: protectedProcedure.input(z6.object({ conversationId: ConversationIdSchema })).mutation(({ input }) => {
|
|
7492
5775
|
if (process.env.RUNEYA_DEBUG) console.log("[chat]: stopSession");
|
|
7493
5776
|
const killed = activeSessionRegistry.kill(input.conversationId);
|
|
7494
5777
|
if (!killed) throw new TRPCError4({ code: "NOT_FOUND", message: "No active session for this conversation" });
|
|
7495
5778
|
return { ok: true };
|
|
7496
5779
|
}),
|
|
7497
|
-
streamViaClaudeCode: protectedProcedure.input(
|
|
5780
|
+
streamViaClaudeCode: protectedProcedure.input(z6.object({
|
|
7498
5781
|
conversationId: ConversationIdSchema,
|
|
7499
|
-
prompt:
|
|
7500
|
-
contextNote:
|
|
5782
|
+
prompt: z6.string().max(1e5),
|
|
5783
|
+
contextNote: z6.string().max(2e5).optional(),
|
|
7501
5784
|
model: ShortString200Schema.optional(),
|
|
7502
|
-
effort:
|
|
7503
|
-
reconnect:
|
|
5785
|
+
effort: z6.enum(["low", "medium", "high"]).optional(),
|
|
5786
|
+
reconnect: z6.boolean().optional(),
|
|
7504
5787
|
agentId: Id128Schema.optional(),
|
|
7505
|
-
environmentId:
|
|
7506
|
-
imageIds:
|
|
5788
|
+
environmentId: z6.string().max(128).optional(),
|
|
5789
|
+
imageIds: z6.array(z6.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)).max(5).optional(),
|
|
7507
5790
|
imageParts: ImagePartsSchema
|
|
7508
5791
|
})).subscription(async function* ({ input, ctx, signal }) {
|
|
7509
5792
|
if (process.env.RUNEYA_DEBUG) console.log("[chat]: Reconnect");
|
|
@@ -7605,15 +5888,15 @@ ${imagePaths.map((p) => `Image: ${p}`).join("\n")}` : cliPrompt;
|
|
|
7605
5888
|
if (process.env.RUNEYA_DEBUG) console.log("[chat]: drainSession...");
|
|
7606
5889
|
yield* drainSession(session, signal, false);
|
|
7607
5890
|
}),
|
|
7608
|
-
streamViaCodex: protectedProcedure.input(
|
|
5891
|
+
streamViaCodex: protectedProcedure.input(z6.object({
|
|
7609
5892
|
conversationId: ConversationIdSchema,
|
|
7610
|
-
prompt:
|
|
7611
|
-
contextNote:
|
|
5893
|
+
prompt: z6.string().max(1e5),
|
|
5894
|
+
contextNote: z6.string().max(2e5).optional(),
|
|
7612
5895
|
model: ShortString200Schema.optional(),
|
|
7613
|
-
reconnect:
|
|
5896
|
+
reconnect: z6.boolean().optional(),
|
|
7614
5897
|
agentId: Id128Schema.optional(),
|
|
7615
|
-
environmentId:
|
|
7616
|
-
imageIds:
|
|
5898
|
+
environmentId: z6.string().max(128).optional(),
|
|
5899
|
+
imageIds: z6.array(z6.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)).max(5).optional(),
|
|
7617
5900
|
imageParts: ImagePartsSchema
|
|
7618
5901
|
})).subscription(async function* ({ input, ctx, signal }) {
|
|
7619
5902
|
const existingSession = activeSessionRegistry.get(input.conversationId);
|
|
@@ -7683,13 +5966,13 @@ ${input.prompt}` : input.prompt;
|
|
|
7683
5966
|
});
|
|
7684
5967
|
yield* drainSession(session, signal, false);
|
|
7685
5968
|
}),
|
|
7686
|
-
send: protectedProcedure.input(
|
|
5969
|
+
send: protectedProcedure.input(z6.object({
|
|
7687
5970
|
conversationId: ConversationIdSchema,
|
|
7688
|
-
messages:
|
|
5971
|
+
messages: z6.array(MessageSchema).min(0).max(100),
|
|
7689
5972
|
model: ShortString200Schema.optional(),
|
|
7690
|
-
contextNote:
|
|
7691
|
-
reconnect:
|
|
7692
|
-
imageIds:
|
|
5973
|
+
contextNote: z6.string().max(2e5).optional(),
|
|
5974
|
+
reconnect: z6.boolean().optional(),
|
|
5975
|
+
imageIds: z6.array(z6.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)).max(5).optional(),
|
|
7693
5976
|
imageParts: ImagePartsSchema
|
|
7694
5977
|
})).subscription(async function* ({ input, ctx, signal }) {
|
|
7695
5978
|
const existingSession = activeSessionRegistry.get(input.conversationId);
|
|
@@ -7806,26 +6089,31 @@ ${input.prompt}` : input.prompt;
|
|
|
7806
6089
|
// ── PTY passthrough (interactive Claude Code session) ────────────────────
|
|
7807
6090
|
pty: router({
|
|
7808
6091
|
/** Start a PTY session running `claude --resume <sessionId>` (or fresh). */
|
|
7809
|
-
start: protectedProcedure.input(
|
|
7810
|
-
agentId:
|
|
7811
|
-
sessionId:
|
|
7812
|
-
runner:
|
|
7813
|
-
resume:
|
|
7814
|
-
model:
|
|
7815
|
-
cwd:
|
|
6092
|
+
start: protectedProcedure.input(z6.object({
|
|
6093
|
+
agentId: z6.string().max(128).optional(),
|
|
6094
|
+
sessionId: z6.string().max(128).optional(),
|
|
6095
|
+
runner: z6.enum(["claude-code", "codex"]).default("claude-code"),
|
|
6096
|
+
resume: z6.boolean().default(false),
|
|
6097
|
+
model: z6.string().max(128).optional(),
|
|
6098
|
+
cwd: z6.string().max(4096).optional(),
|
|
7816
6099
|
/** Anchors the session when no explicit cwd is given. */
|
|
7817
|
-
serviceId:
|
|
6100
|
+
serviceId: z6.string().max(128).optional(),
|
|
7818
6101
|
projectId: Id128Schema.optional(),
|
|
7819
|
-
orgId:
|
|
7820
|
-
cols:
|
|
7821
|
-
rows:
|
|
7822
|
-
enableMcp:
|
|
7823
|
-
apiBaseUrl:
|
|
7824
|
-
environmentId:
|
|
7825
|
-
attachedContexts:
|
|
7826
|
-
label:
|
|
7827
|
-
text:
|
|
7828
|
-
})).max(20).optional()
|
|
6102
|
+
orgId: z6.string().max(128).nullable().optional(),
|
|
6103
|
+
cols: z6.number().int().min(1).max(1e3).default(120),
|
|
6104
|
+
rows: z6.number().int().min(1).max(1e3).default(30),
|
|
6105
|
+
enableMcp: z6.boolean().default(false),
|
|
6106
|
+
apiBaseUrl: z6.string().max(512).optional(),
|
|
6107
|
+
environmentId: z6.string().max(128).optional(),
|
|
6108
|
+
attachedContexts: z6.array(z6.object({
|
|
6109
|
+
label: z6.string().max(200),
|
|
6110
|
+
text: z6.string().max(1e6)
|
|
6111
|
+
})).max(20).optional(),
|
|
6112
|
+
/**
|
|
6113
|
+
* Prompt collé puis soumis dès que le CLI accepte la saisie (remplissage
|
|
6114
|
+
* d'un tableau kanban). Ignoré quand on se rattache à un terminal existant.
|
|
6115
|
+
*/
|
|
6116
|
+
initialPrompt: z6.string().max(1e6).optional()
|
|
7829
6117
|
})).mutation(async ({ input, ctx }) => {
|
|
7830
6118
|
const t0 = Date.now();
|
|
7831
6119
|
const lap = (label) => {
|
|
@@ -7897,6 +6185,8 @@ ${input.prompt}` : input.prompt;
|
|
|
7897
6185
|
}
|
|
7898
6186
|
const injectedEnv = await resolveInjectedEnv(input.environmentId);
|
|
7899
6187
|
lap("resolveInjectedEnv");
|
|
6188
|
+
const hasStartupInput = (input.attachedContexts?.length ?? 0) > 0 || !!input.initialPrompt?.trim();
|
|
6189
|
+
const mcpReadyId = input.enableMcp && hasStartupInput ? randomUUID5() : void 0;
|
|
7900
6190
|
const res = await agentManager.agentMutation(
|
|
7901
6191
|
agentId,
|
|
7902
6192
|
"ai.spawnPty",
|
|
@@ -7922,74 +6212,32 @@ ${input.prompt}` : input.prompt;
|
|
|
7922
6212
|
mcpApiBaseUrl: input.apiBaseUrl ?? `http://localhost:${env.PORT}`,
|
|
7923
6213
|
mcpApiToken: jwt,
|
|
7924
6214
|
...ptyRef?.id ? { mcpProjectId: ptyRef.id } : {},
|
|
7925
|
-
...ptyRef?.orgId ? { mcpOrgId: ptyRef.orgId } : {}
|
|
6215
|
+
...ptyRef?.orgId ? { mcpOrgId: ptyRef.orgId } : {},
|
|
6216
|
+
// Un agent ancien ignore ce champ : le plafond de `waitForPtyInput` couvre ce cas.
|
|
6217
|
+
...mcpReadyId ? { mcpReadyId } : {}
|
|
7926
6218
|
}
|
|
7927
6219
|
);
|
|
7928
6220
|
lap("agent ai.spawnPty");
|
|
7929
|
-
const contexts =
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
const injection = `\x1B[200~---------
|
|
7934
|
-
Context:
|
|
7935
|
-
\x1B[201~\x1B[200~${body}\x1B[201~\x1B[200~
|
|
7936
|
-
---------
|
|
7937
|
-
\x1B[201~`;
|
|
7938
|
-
const doInject = () => {
|
|
7939
|
-
agentManager.agentMutation(agentId, "ai.writePty", { spawnId: res.spawnId, data: injection }).catch((err) => {
|
|
7940
|
-
console.warn("[chat.pty.start] deferred context writePty failed:", err.message);
|
|
7941
|
-
});
|
|
7942
|
-
};
|
|
7943
|
-
const startedAt = Date.now();
|
|
7944
|
-
const MIN_MS = input.runner === "codex" ? 500 : 1200;
|
|
7945
|
-
const QUIET_MS = 400;
|
|
7946
|
-
const MAX_MS = 8e3;
|
|
7947
|
-
let injected = false;
|
|
7948
|
-
let quietTimer = null;
|
|
7949
|
-
let maxTimer = null;
|
|
7950
|
-
let unsub = null;
|
|
7951
|
-
const cleanup = () => {
|
|
7952
|
-
if (quietTimer) clearTimeout(quietTimer);
|
|
7953
|
-
if (maxTimer) clearTimeout(maxTimer);
|
|
7954
|
-
unsub?.();
|
|
7955
|
-
};
|
|
7956
|
-
const inject = () => {
|
|
7957
|
-
if (injected) return;
|
|
7958
|
-
injected = true;
|
|
7959
|
-
cleanup();
|
|
7960
|
-
doInject();
|
|
7961
|
-
};
|
|
7962
|
-
const scheduleQuiet = () => {
|
|
7963
|
-
if (quietTimer) clearTimeout(quietTimer);
|
|
7964
|
-
const wait = Math.max(QUIET_MS, MIN_MS - (Date.now() - startedAt));
|
|
7965
|
-
quietTimer = setTimeout(inject, wait);
|
|
7966
|
-
};
|
|
7967
|
-
maxTimer = setTimeout(inject, MAX_MS);
|
|
7968
|
-
unsub = agentManager.agentSubscribe(
|
|
6221
|
+
const contexts = input.attachedContexts ?? [];
|
|
6222
|
+
const initialPrompt = input.initialPrompt?.trim() ? input.initialPrompt : void 0;
|
|
6223
|
+
if (contexts.length > 0 || initialPrompt) {
|
|
6224
|
+
void writeWhenPtyReady({
|
|
7969
6225
|
agentId,
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
cleanup();
|
|
7977
|
-
return;
|
|
7978
|
-
}
|
|
7979
|
-
scheduleQuiet();
|
|
7980
|
-
},
|
|
7981
|
-
() => {
|
|
7982
|
-
}
|
|
7983
|
-
);
|
|
6226
|
+
spawnId: res.spawnId,
|
|
6227
|
+
runner: input.runner,
|
|
6228
|
+
mcpReadyId,
|
|
6229
|
+
contexts,
|
|
6230
|
+
initialPrompt
|
|
6231
|
+
});
|
|
7984
6232
|
}
|
|
7985
6233
|
noteSpawnConversation(res.spawnId, input.sessionId);
|
|
7986
6234
|
return { spawnId: res.spawnId, agentId, reattached: false };
|
|
7987
6235
|
}),
|
|
7988
6236
|
/** Subscribe to PTY output stream (raw text with ANSI). */
|
|
7989
|
-
stream: protectedProcedure.input(
|
|
7990
|
-
agentId:
|
|
7991
|
-
spawnId:
|
|
7992
|
-
fromOffset:
|
|
6237
|
+
stream: protectedProcedure.input(z6.object({
|
|
6238
|
+
agentId: z6.string().max(128).optional(),
|
|
6239
|
+
spawnId: z6.string().max(128),
|
|
6240
|
+
fromOffset: z6.number().int().min(0).default(0)
|
|
7993
6241
|
})).subscription(async function* ({ input, signal }) {
|
|
7994
6242
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7995
6243
|
if (!agentId) throw new TRPCError4({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
@@ -8044,10 +6292,10 @@ Context:
|
|
|
8044
6292
|
}
|
|
8045
6293
|
}),
|
|
8046
6294
|
/** Write input to a PTY session (keystrokes from xterm). */
|
|
8047
|
-
write: protectedProcedure.input(
|
|
8048
|
-
agentId:
|
|
8049
|
-
spawnId:
|
|
8050
|
-
data:
|
|
6295
|
+
write: protectedProcedure.input(z6.object({
|
|
6296
|
+
agentId: z6.string().max(128).optional(),
|
|
6297
|
+
spawnId: z6.string().max(128),
|
|
6298
|
+
data: z6.string().max(1e6)
|
|
8051
6299
|
})).mutation(async ({ input }) => {
|
|
8052
6300
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
8053
6301
|
if (!agentId) throw new TRPCError4({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
@@ -8057,11 +6305,11 @@ Context:
|
|
|
8057
6305
|
});
|
|
8058
6306
|
}),
|
|
8059
6307
|
/** Resize a PTY session. */
|
|
8060
|
-
resize: protectedProcedure.input(
|
|
8061
|
-
agentId:
|
|
8062
|
-
spawnId:
|
|
8063
|
-
cols:
|
|
8064
|
-
rows:
|
|
6308
|
+
resize: protectedProcedure.input(z6.object({
|
|
6309
|
+
agentId: z6.string().max(128).optional(),
|
|
6310
|
+
spawnId: z6.string().max(128),
|
|
6311
|
+
cols: z6.number().int().min(1).max(1e3),
|
|
6312
|
+
rows: z6.number().int().min(1).max(1e3)
|
|
8065
6313
|
})).mutation(async ({ input }) => {
|
|
8066
6314
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
8067
6315
|
if (!agentId) throw new TRPCError4({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
@@ -8077,11 +6325,11 @@ Context:
|
|
|
8077
6325
|
* picks it up. Returns the tmp path and which clipboard method succeeded
|
|
8078
6326
|
* (null if none — caller should inject `@<path>` instead of Ctrl+V).
|
|
8079
6327
|
*/
|
|
8080
|
-
pasteImage: protectedProcedure.input(
|
|
8081
|
-
agentId:
|
|
8082
|
-
sessionId:
|
|
8083
|
-
mimeType:
|
|
8084
|
-
base64:
|
|
6328
|
+
pasteImage: protectedProcedure.input(z6.object({
|
|
6329
|
+
agentId: z6.string().max(128).optional(),
|
|
6330
|
+
sessionId: z6.string().max(128).optional(),
|
|
6331
|
+
mimeType: z6.string().max(64),
|
|
6332
|
+
base64: z6.string().max(15e6)
|
|
8085
6333
|
})).mutation(async ({ input }) => {
|
|
8086
6334
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
8087
6335
|
if (!agentId) throw new TRPCError4({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
@@ -8099,7 +6347,7 @@ Context:
|
|
|
8099
6347
|
* Read the transcript Claude Code stores on disk for a given session.
|
|
8100
6348
|
* Returns parsed JSONL entries (user / assistant messages, tool uses, etc.).
|
|
8101
6349
|
*/
|
|
8102
|
-
transcript: protectedProcedure.input(
|
|
6350
|
+
transcript: protectedProcedure.input(z6.object({ sessionId: z6.string().max(128) })).query(async ({ input }) => {
|
|
8103
6351
|
return readClaudeTranscript(input.sessionId);
|
|
8104
6352
|
}),
|
|
8105
6353
|
/**
|
|
@@ -8107,9 +6355,9 @@ Context:
|
|
|
8107
6355
|
* when was the transcript JSONL last touched? Used by the non-PTY view
|
|
8108
6356
|
* to show a "Claude is thinking…" indicator while output streams.
|
|
8109
6357
|
*/
|
|
8110
|
-
activity: protectedProcedure.input(
|
|
8111
|
-
agentId:
|
|
8112
|
-
sessionId:
|
|
6358
|
+
activity: protectedProcedure.input(z6.object({
|
|
6359
|
+
agentId: z6.string().max(128).optional(),
|
|
6360
|
+
sessionId: z6.string().max(128)
|
|
8113
6361
|
})).query(async ({ input }) => {
|
|
8114
6362
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
8115
6363
|
let running = false;
|
|
@@ -8136,7 +6384,7 @@ Context:
|
|
|
8136
6384
|
* them on the conversation, so the non-PTY chat view stays in sync.
|
|
8137
6385
|
* Uses the conversation id as session id (PTY sessions are spawned that way).
|
|
8138
6386
|
*/
|
|
8139
|
-
syncTranscript: protectedProcedure.input(
|
|
6387
|
+
syncTranscript: protectedProcedure.input(z6.object({ conversationId: ConversationIdSchema })).mutation(async ({ input }) => {
|
|
8140
6388
|
const conv = await conversationStore.get(input.conversationId);
|
|
8141
6389
|
if (!conv) {
|
|
8142
6390
|
throw new TRPCError4({ code: "NOT_FOUND", message: `Conversation ${input.conversationId} not found` });
|
|
@@ -8150,14 +6398,14 @@ Context:
|
|
|
8150
6398
|
* with the given sessionId. Used to benchmark the PTY → tRPC → xterm.js
|
|
8151
6399
|
* pipeline. Open the conversation in PTY mode to attach to it.
|
|
8152
6400
|
*/
|
|
8153
|
-
debugStress: protectedProcedure.input(
|
|
8154
|
-
sessionId:
|
|
8155
|
-
agentId:
|
|
8156
|
-
bytesPerSec:
|
|
8157
|
-
chunkSize:
|
|
8158
|
-
durationMs:
|
|
8159
|
-
cols:
|
|
8160
|
-
rows:
|
|
6401
|
+
debugStress: protectedProcedure.input(z6.object({
|
|
6402
|
+
sessionId: z6.string().max(128),
|
|
6403
|
+
agentId: z6.string().max(128).optional(),
|
|
6404
|
+
bytesPerSec: z6.number().int().min(100).max(1e7).default(5e4),
|
|
6405
|
+
chunkSize: z6.number().int().min(1).max(65536).default(256),
|
|
6406
|
+
durationMs: z6.number().int().min(0).max(36e5).default(0),
|
|
6407
|
+
cols: z6.number().int().min(1).max(1e3).default(120),
|
|
6408
|
+
rows: z6.number().int().min(1).max(1e3).default(30)
|
|
8161
6409
|
})).mutation(async ({ input }) => {
|
|
8162
6410
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
8163
6411
|
if (!agentId) throw new TRPCError4({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
@@ -8204,9 +6452,9 @@ Context:
|
|
|
8204
6452
|
return { spawnId: res.spawnId, agentId, stressSessionId };
|
|
8205
6453
|
}),
|
|
8206
6454
|
/** Kill a PTY session. */
|
|
8207
|
-
kill: protectedProcedure.input(
|
|
8208
|
-
agentId:
|
|
8209
|
-
spawnId:
|
|
6455
|
+
kill: protectedProcedure.input(z6.object({
|
|
6456
|
+
agentId: z6.string().max(128).optional(),
|
|
6457
|
+
spawnId: z6.string().max(128)
|
|
8210
6458
|
})).mutation(async ({ input }) => {
|
|
8211
6459
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
8212
6460
|
if (!agentId) throw new TRPCError4({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
@@ -8224,7 +6472,9 @@ export {
|
|
|
8224
6472
|
router,
|
|
8225
6473
|
publicProcedure,
|
|
8226
6474
|
protectedProcedure,
|
|
6475
|
+
requireLoopback,
|
|
8227
6476
|
requireLoopbackOrToken,
|
|
6477
|
+
requireHuman,
|
|
8228
6478
|
requireServiceAction,
|
|
8229
6479
|
getAllowedServiceIds,
|
|
8230
6480
|
requireEnvAction,
|
|
@@ -8239,9 +6489,6 @@ export {
|
|
|
8239
6489
|
cloudFetch,
|
|
8240
6490
|
readCloudIdentity,
|
|
8241
6491
|
attachServiceToProject,
|
|
8242
|
-
environmentOverridesStore,
|
|
8243
|
-
filterEnvForAi,
|
|
8244
|
-
environmentResolver,
|
|
8245
6492
|
auditService,
|
|
8246
6493
|
isShareable,
|
|
8247
6494
|
logParserStore,
|
|
@@ -8254,6 +6501,7 @@ export {
|
|
|
8254
6501
|
networkInfo,
|
|
8255
6502
|
applyPersistedLanMode,
|
|
8256
6503
|
setLanMode,
|
|
6504
|
+
runeyaMcpConfig,
|
|
8257
6505
|
Id128Schema,
|
|
8258
6506
|
ConversationIdSchema,
|
|
8259
6507
|
NameSchema,
|
|
@@ -8276,6 +6524,8 @@ export {
|
|
|
8276
6524
|
kanbanStore,
|
|
8277
6525
|
executeKanbanCommands,
|
|
8278
6526
|
executeKanbanBoardCommands,
|
|
6527
|
+
buildKanbanCardPrompt,
|
|
6528
|
+
kanbanContextFor,
|
|
8279
6529
|
ClaudeCodeRunner,
|
|
8280
6530
|
shutdownCodexAppServer,
|
|
8281
6531
|
CodexRunner,
|
|
@@ -8285,4 +6535,4 @@ export {
|
|
|
8285
6535
|
startCliSession,
|
|
8286
6536
|
chatRouter
|
|
8287
6537
|
};
|
|
8288
|
-
//# sourceMappingURL=chunk-
|
|
6538
|
+
//# sourceMappingURL=chunk-2NE52CL5.js.map
|