@runeya/runeya 2.0.146 → 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.
Files changed (171) hide show
  1. package/agent/index.js +10 -4
  2. package/agent/index.js.map +1 -1
  3. package/{agent-manager-R7BPQO6D.js → agent-manager-46C4NIQ2.js} +4 -2
  4. package/{chat-LPML22J5.js → chat-CMNDMXIP.js} +9 -5
  5. package/{chunk-SEPAURTQ.js → chunk-2NE52CL5.js} +156 -923
  6. package/chunk-2NE52CL5.js.map +1 -0
  7. package/chunk-4D3GCBPO.js +462 -0
  8. package/chunk-4D3GCBPO.js.map +1 -0
  9. package/{chunk-KV4GJKXU.js → chunk-5XG3X2TM.js} +27 -1479
  10. package/chunk-5XG3X2TM.js.map +1 -0
  11. package/chunk-7QXP4FXD.js +969 -0
  12. package/chunk-7QXP4FXD.js.map +1 -0
  13. package/chunk-DA2DZBTL.js +43 -0
  14. package/chunk-DA2DZBTL.js.map +1 -0
  15. package/chunk-DFPZ4MTI.js +1041 -0
  16. package/chunk-DFPZ4MTI.js.map +1 -0
  17. package/{chunk-UD42LACH.js → chunk-JNK6H5KH.js} +164 -1007
  18. package/chunk-JNK6H5KH.js.map +1 -0
  19. package/chunk-WDHLEO5S.js +809 -0
  20. package/chunk-WDHLEO5S.js.map +1 -0
  21. package/{companion-W3UMZWGH.js → companion-XKMNRIJB.js} +8 -3
  22. package/environment-resolver-ZDTF3MHI.js +18 -0
  23. package/environment-resolver-ZDTF3MHI.js.map +1 -0
  24. package/index.js +7 -7
  25. package/{install-gate-PVMGJDWE.js → install-gate-OJJEUU4E.js} +2 -2
  26. package/mcp-ready-TAPBAOZY.js +9 -0
  27. package/mcp-ready-TAPBAOZY.js.map +1 -0
  28. package/mcp-server/index.js +24 -0
  29. package/mcp-server/index.js.map +1 -1
  30. package/mcp-server/launch.d.ts +6 -0
  31. package/mcp-server/launch.js +2 -1
  32. package/mcp-server/launch.js.map +1 -1
  33. package/package.json +1 -1
  34. package/project-store-FLXTAD3J.js +23 -0
  35. package/project-store-FLXTAD3J.js.map +1 -0
  36. package/{src-LEDGW5KP.js → src-KWO3CKZU.js} +139 -59
  37. package/src-KWO3CKZU.js.map +1 -0
  38. package/web/assets/{AgentScalarChatInterface.vue-BYszptaB.js → AgentScalarChatInterface.vue-DKBinB_l.js} +2 -2
  39. package/web/assets/AgentTerminalPage-CixU7x6O.js +1 -0
  40. package/web/assets/AiChatModals-BvGUd-QD.css +1 -0
  41. package/web/assets/AiChatModals-C-hXImkO.js +4 -0
  42. package/web/assets/AppLayout-At9wk0sx.js +1 -0
  43. package/web/assets/{AppLayout-tsw5Ow0w.css → AppLayout-BlhwflMq.css} +1 -1
  44. package/web/assets/AuditPage-BkqbHxjY.js +1 -0
  45. package/web/assets/CloudAccountPage-D5C2SCS9.js +1 -0
  46. package/web/assets/CloudMessagingPage-Bi57GGHO.js +2 -0
  47. package/web/assets/DashboardPage-dEQop9ql.js +1 -0
  48. package/web/assets/DirectoryPickerDialog-DIMSeDhw.js +1 -0
  49. package/web/assets/{EditorIcon-CypGLV6w.js → EditorIcon-W3Szca4J.js} +1 -1
  50. package/web/assets/EnvironmentForm-C5emavNh.js +1 -0
  51. package/web/assets/EnvironmentFormPage-BIoy8fsW.js +1 -0
  52. package/web/assets/EnvironmentListPage-BDYeh7XF.js +1 -0
  53. package/web/assets/{FlagChip-R_T5qYpL.js → FlagChip-DPtIBCjQ.js} +1 -1
  54. package/web/assets/KanbanBoardPage-BlCcjBwV.js +4 -0
  55. package/web/assets/KanbanListPage-DbXONxfC.js +1 -0
  56. package/web/assets/LaunchProjectPage-Cg92hHwQ.js +1 -0
  57. package/web/assets/LaunchServicesPage-CO8gOB9D.js +1 -0
  58. package/web/assets/LaunchServicesPage-h5o3zLcy.css +1 -0
  59. package/web/assets/{LaunchShell-DUrSunB4.js → LaunchShell-CDb21ihZ.js} +1 -1
  60. package/web/assets/LogViewer-DsusKk2b.js +3 -0
  61. package/web/assets/OrgKeyPrompt-CX9K7r9I.js +1 -0
  62. package/web/assets/ProcessControls-B7hWVqiR.css +1 -0
  63. package/web/assets/ProcessControls-TEZutuxq.js +1 -0
  64. package/web/assets/ProcessDetailPage-Cj23FECq.js +1 -0
  65. package/web/assets/ProcessMetrics-1P6iE2Il.js +1 -0
  66. package/web/assets/ProjectCreateDialog-D3aMWKfw.js +1 -0
  67. package/web/assets/{ProjectLocationGate-DhMVdIjN.js → ProjectLocationGate-DU0Jj8Nw.js} +1 -1
  68. package/web/assets/ProjectPublishDialog-D7sNKei3.js +1 -0
  69. package/web/assets/{RandomJobTestPage-BUS2Ty6-.js → RandomJobTestPage-DQUMRHyf.js} +1 -1
  70. package/web/assets/{ScenarioEditorPage-vMoRt0Qn.css → ScenarioEditorPage-BSjSX4bw.css} +1 -1
  71. package/web/assets/ScenarioEditorPage-DvCdcY9m.js +14 -0
  72. package/web/assets/SendToChatDialog-BdWKjYmv.js +1 -0
  73. package/web/assets/ServiceControlsBar-B_J5m-Kc.js +1 -0
  74. package/web/assets/ServiceDetailPage-DhJcjFfY.js +2 -0
  75. package/web/assets/{ServiceDetailPage-COdaCZ6r.css → ServiceDetailPage-DnSf5cm9.css} +1 -1
  76. package/web/assets/ServiceFormPage-fp8NCHSv.js +1 -0
  77. package/web/assets/{SettingsPage-4pKUgKPm.css → SettingsPage-NH2fQap4.css} +1 -1
  78. package/web/assets/SettingsPage-s28BodWe.js +2086 -0
  79. package/web/assets/UnlockPage-BXXIPHW_.js +1 -0
  80. package/web/assets/VaultMigrationPage-BfNuKtkJ.js +1 -0
  81. package/web/assets/{VersionBadge-BmArk24p.js → VersionBadge-CW7czacg.js} +1 -1
  82. package/web/assets/{WikiPage-DADu8WUA.js → WikiPage-BiwpCM3E.js} +1 -1
  83. package/web/assets/WikiView-CMch_06Z.js +1 -0
  84. package/web/assets/{add-scalar-classes-dWEWPlFD.js → add-scalar-classes-Ckanjq1h.js} +14 -14
  85. package/web/assets/{agent.store-CKLAkF9a.js → agent.store-piokQ9Dt.js} +1 -1
  86. package/web/assets/{auth-Cg9BWTfK.js → auth-BT_jf-5e.js} +2 -2
  87. package/web/assets/cloud-chat.store-CDKh4tIy.js +1 -0
  88. package/web/assets/{cloud-client-D_zCiW0s.js → cloud-client-CzPdZLDq.js} +2 -2
  89. package/web/assets/{cloud-session-DOaDpZTy.js → cloud-session-BYfn6YM4.js} +2 -2
  90. package/web/assets/floating-ui.dom-DF3oyfHd.js +1 -0
  91. package/web/assets/i18n-D0FT7rCj.js +1 -0
  92. package/web/assets/index-3LTzgeU-.css +1 -0
  93. package/web/assets/index-Ci2CNnit.js +309 -0
  94. package/web/assets/modal-iRaP1izE.js +5 -0
  95. package/web/assets/notification.store-CvvuTBVz.js +1 -0
  96. package/web/assets/{project-launch.store-y7JBMNiE.js → project-launch.store-CTmbl8UY.js} +1 -1
  97. package/web/assets/{project.store-DGJr0Qr9.js → project.store-CNC6_4r4.js} +1 -1
  98. package/web/assets/runtime-dom.esm-bundler-CyEVRK6B.js +1 -0
  99. package/web/assets/scenario.store-BIFQPXgl.js +7 -0
  100. package/web/assets/{service.store-5jWsJxPx.js → service.store-B40U4QzI.js} +1 -1
  101. package/web/assets/settings-scope-CyWMCjRY.js +1 -0
  102. package/web/assets/settings.store-Dw6bAoWE.js +1 -0
  103. package/web/assets/{src-Dnwk6NpV.js → src-CrNi4Pjg.js} +3 -3
  104. package/web/assets/src-DIBHat7I.js +3 -0
  105. package/web/assets/src-DtCW_XCh.js +1 -0
  106. package/web/assets/{trpc-Drabzwpd.js → trpc-BAZGRZBD.js} +3 -3
  107. package/web/assets/{useCloudOrg-BAQEtFPl.js → useCloudOrg-EnXFBwt1.js} +1 -1
  108. package/web/assets/useEnvVarAutocomplete-BxJh2Lw0.js +1 -0
  109. package/web/assets/useProjectCatalog-BrvUGv9A.js +1 -0
  110. package/web/assets/{useProjectCloudOrg-CEN9-_OA.js → useProjectCloudOrg-Czxlg00k.js} +1 -1
  111. package/web/assets/{useServiceUrlHost-BuoPEXZX.js → useServiceUrlHost-Szz9y3rh.js} +1 -1
  112. package/web/assets/vue-i18n-o7yraQpR.js +3 -0
  113. package/web/index.html +21 -21
  114. package/chunk-KV4GJKXU.js.map +0 -1
  115. package/chunk-SEPAURTQ.js.map +0 -1
  116. package/chunk-UD42LACH.js.map +0 -1
  117. package/src-LEDGW5KP.js.map +0 -1
  118. package/web/assets/AgentTerminalPage-C3TJ1NL9.js +0 -1
  119. package/web/assets/AiChatModals-D3scRE3S.js +0 -4
  120. package/web/assets/AiChatModals-EfRuvId7.css +0 -1
  121. package/web/assets/AppLayout-BMQUjPKe.js +0 -1
  122. package/web/assets/AuditPage-CKcExBws.js +0 -1
  123. package/web/assets/CloudAccountPage-B6oPg0BZ.js +0 -1
  124. package/web/assets/CloudMessagingPage-C5fwVe-F.js +0 -2
  125. package/web/assets/DashboardPage-9RV7sx3e.js +0 -1
  126. package/web/assets/DirectoryPickerDialog-C06BXyfJ.js +0 -1
  127. package/web/assets/EnvironmentForm-CAvDoira.js +0 -1
  128. package/web/assets/EnvironmentFormPage-BmAxhN-m.js +0 -1
  129. package/web/assets/EnvironmentListPage-B8uATPBI.js +0 -1
  130. package/web/assets/KanbanBoardPage-CQsOkRUf.js +0 -4
  131. package/web/assets/KanbanListPage-CWZqkjD6.js +0 -1
  132. package/web/assets/LaunchProjectPage-saSmC7Pr.js +0 -1
  133. package/web/assets/LaunchServicesPage-BkKyLMEz.js +0 -1
  134. package/web/assets/LaunchServicesPage-DoLDPqQq.css +0 -1
  135. package/web/assets/LogViewer-CZBYkH9J.js +0 -3
  136. package/web/assets/OrgKeyPrompt-DzQ7pwbs.js +0 -1
  137. package/web/assets/ProcessControls-BEeoNlNV.js +0 -1
  138. package/web/assets/ProcessControls-BlpnVylq.css +0 -1
  139. package/web/assets/ProcessDetailPage-CQgu5uKm.js +0 -1
  140. package/web/assets/ProcessMetrics-Crlfkelj.js +0 -1
  141. package/web/assets/ProjectCreateDialog-BolrhRD-.js +0 -1
  142. package/web/assets/ProjectPublishDialog-DsZNWrfz.js +0 -1
  143. package/web/assets/ScenarioEditorPage-MIAGh2us.js +0 -14
  144. package/web/assets/SendToChatDialog-BxWsyv29.js +0 -1
  145. package/web/assets/ServiceControlsBar-DqDPAZt7.js +0 -1
  146. package/web/assets/ServiceDetailPage-hkSAzZ-l.js +0 -2
  147. package/web/assets/ServiceFormPage-DXqhqXg8.js +0 -1
  148. package/web/assets/SettingsPage-DS2YhMIM.js +0 -2086
  149. package/web/assets/UnlockPage-s8SgVcKk.js +0 -1
  150. package/web/assets/VaultMigrationPage-s0b44MDM.js +0 -1
  151. package/web/assets/WikiView-DtGwIt_Q.js +0 -1
  152. package/web/assets/cloud-chat.store-b-BHGz7I.js +0 -1
  153. package/web/assets/editor.store-DCAZSeVG.js +0 -1
  154. package/web/assets/floating-ui.dom-DWhsZyqT.js +0 -1
  155. package/web/assets/i18n-D0afQEGs.js +0 -1
  156. package/web/assets/index-D8VRbyoP.js +0 -309
  157. package/web/assets/index-DsezNbSM.css +0 -1
  158. package/web/assets/modal-qYgTMkYb.js +0 -5
  159. package/web/assets/scenario.store-YWHL2_wu.js +0 -7
  160. package/web/assets/settings-scope-Bv7QGPrt.js +0 -1
  161. package/web/assets/settings.store-bEsB7oKb.js +0 -1
  162. package/web/assets/src--GO-VSJ0.js +0 -5
  163. package/web/assets/src-C-D_y7ou.js +0 -1
  164. package/web/assets/useEnvVarAutocomplete-BqGTODUv.js +0 -1
  165. package/web/assets/useProjectCatalog-yocjIKt6.js +0 -1
  166. package/web/assets/vue-draggable-plus-BHsveB8w.css +0 -1
  167. package/web/assets/vue-draggable-plus-DX4rTBiu.js +0 -1
  168. /package/{agent-manager-R7BPQO6D.js.map → agent-manager-46C4NIQ2.js.map} +0 -0
  169. /package/{chat-LPML22J5.js.map → chat-CMNDMXIP.js.map} +0 -0
  170. /package/{companion-W3UMZWGH.js.map → companion-XKMNRIJB.js.map} +0 -0
  171. /package/{install-gate-PVMGJDWE.js.map → install-gate-OJJEUU4E.js.map} +0 -0
@@ -1,17 +1,7 @@
1
- import {
2
- RUNEYA_CAPABILITIES,
3
- applyProjectAnchor,
4
- buildAnthropicTools,
5
- buildOpenAITools,
6
- buildToolSafetyInstruction,
7
- runCapability,
8
- toRawParams
9
- } from "./chunk-6IBPK6UE.js";
10
- import {
11
- settingsManager
12
- } from "./chunk-NPMSBYXC.js";
13
1
  import {
14
2
  COMPANION_SIGNATURE,
3
+ PASTED_TASK_LEAD,
4
+ PTY_MCP_READY_MAX_MS,
15
5
  activeSessionRegistry,
16
6
  claudeSessionExists,
17
7
  companionCreateDefaults,
@@ -20,7 +10,6 @@ import {
20
10
  companionSkips,
21
11
  companionSystemNote,
22
12
  companionThinkingIds,
23
- conversationSectionStore,
24
13
  conversationStore,
25
14
  deliverToMainConversation,
26
15
  findClaudeSessionFile,
@@ -36,7 +25,7 @@ import {
36
25
  noteSpawnConversation,
37
26
  observePtyChunk,
38
27
  onCompanionThinkingChanged,
39
- projectStore,
28
+ pasteToPty,
40
29
  readClaudeTranscript,
41
30
  resetRelaunches,
42
31
  resolveAgentCwd,
@@ -46,31 +35,54 @@ import {
46
35
  signJwt,
47
36
  stopCompanion,
48
37
  syncConversationFromClaudeTranscript,
49
- verifyJwt
50
- } from "./chunk-UD42LACH.js";
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";
51
60
  import {
52
- StoreDiskCoordinator,
53
61
  agentManager,
54
- agentStore,
62
+ agentStore
63
+ } from "./chunk-5XG3X2TM.js";
64
+ import {
65
+ serviceStore
66
+ } from "./chunk-4D3GCBPO.js";
67
+ import {
68
+ StoreDiskCoordinator,
55
69
  decryptValue,
56
- decryptVariableSlots,
57
70
  encryptValue,
58
- encryptVariableSlots,
59
71
  ensureProjectsLoaded,
60
72
  environmentStore,
61
- extractEncryptedVariableSlots,
62
- getEncryptionKey,
63
73
  hasEncryptionKey,
64
74
  isCiMode,
65
75
  isEncryptedValue,
66
76
  recordLocalRemoval,
67
- refsOfProjectId,
68
- serviceStore
69
- } from "./chunk-KV4GJKXU.js";
77
+ refsOfProjectId
78
+ } from "./chunk-DFPZ4MTI.js";
70
79
  import {
71
80
  isCliAvailable,
72
81
  resolveCliInvocation
73
82
  } from "./chunk-OZBSOSAS.js";
83
+ import {
84
+ settingsManager
85
+ } from "./chunk-NPMSBYXC.js";
74
86
  import {
75
87
  atomicWriteJsonSecure,
76
88
  atomicWriteSecure,
@@ -83,15 +95,13 @@ import {
83
95
  wasRead
84
96
  } from "./chunk-4LJQIQMN.js";
85
97
  import {
86
- allVaultRoots,
87
98
  env,
88
99
  keyedFilePath,
89
100
  legacyMachineFilePath,
90
101
  listOrgVaults,
91
102
  machineFilePath,
92
103
  machineRoot,
93
- orgVaultRoot,
94
- vaultIdOfRoot
104
+ orgVaultRoot
95
105
  } from "./chunk-VPSQJOSN.js";
96
106
  import {
97
107
  AGENT_STOP_FETCH_TIMEOUT,
@@ -101,7 +111,6 @@ import {
101
111
  HealthCheckSchema,
102
112
  JSLogParserSchema,
103
113
  LogSourceConfigSchema,
104
- MASKED_VALUE,
105
114
  MissingVariableSchema,
106
115
  RestartStrategySchema,
107
116
  ScenarioSchema,
@@ -111,7 +120,6 @@ import {
111
120
  crossServiceValues,
112
121
  findUnresolvedReferences,
113
122
  interpolateBuiltinsInEnv,
114
- isSharedWithAi,
115
123
  nativeParsers,
116
124
  restoreLogSourceSecrets,
117
125
  servicePortValues,
@@ -126,6 +134,7 @@ import { z as z6 } from "zod";
126
134
  import Anthropic2 from "@anthropic-ai/sdk";
127
135
  import OpenAI2 from "openai";
128
136
  import { fileURLToPath as fileURLToPath2 } from "url";
137
+ import { randomUUID as randomUUID5 } from "crypto";
129
138
 
130
139
  // ../../packages/mcp-server/src/launch.ts
131
140
  import { fileURLToPath } from "url";
@@ -157,7 +166,8 @@ function pickEnv(env2) {
157
166
  ...env2.RUNEYA_BASE_URL ? { RUNEYA_BASE_URL: env2.RUNEYA_BASE_URL } : {},
158
167
  ...env2.RUNEYA_API_TOKEN ? { RUNEYA_API_TOKEN: env2.RUNEYA_API_TOKEN } : {},
159
168
  ...env2.RUNEYA_PROJECT_ID ? { RUNEYA_PROJECT_ID: env2.RUNEYA_PROJECT_ID } : {},
160
- ...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 } : {}
161
171
  };
162
172
  }
163
173
  function runeyaMcpConfig(env2) {
@@ -621,783 +631,6 @@ var ShortString200Schema = z2.string().max(200);
621
631
  var DescriptionSchema = z2.string().max(2e3);
622
632
  var OptionalProviderIdSchema = Id128Schema.optional();
623
633
 
624
- // ../server/src/services/environment-overrides-store.ts
625
- import { readFile as readFile2, writeFile, mkdir, chmod } from "fs/promises";
626
- import { dirname, join as join3 } from "path";
627
- var FILENAME = "environment-overrides.json";
628
- var EnvironmentOverridesStore = class {
629
- data = {};
630
- loaded = false;
631
- /**
632
- * Le coordinateur partagé par les stores : un seul rechargement à la fois,
633
- * jamais pendant une écriture, et une substitution atomique à la fin.
634
- *
635
- * Ce store faisait sa propre cuisine et se marquait « chargé » avant ses
636
- * `await` : toute lecture concurrente repartait avec une table vide — une
637
- * surcharge déjà écrite paraissait absente, et l'assistant réclamait au
638
- * déverrouillage des variables que ce poste avait déjà renseignées.
639
- */
640
- disk = new StoreDiskCoordinator();
641
- /** Ce que le disque portait au dernier chargement (cf. `load`). */
642
- fingerprint = "";
643
- diskEncryptedSlots = {};
644
- /**
645
- * Les surcharges dont le secret ne s'est pas ouvert.
646
- *
647
- * Vides en mémoire, elles doivent repartir chiffrées : sans cela, la
648
- * sauvegarde suivante remplacerait par du blanc une valeur dont il ne
649
- * manquait que la clé.
650
- */
651
- unreadableSecrets = /* @__PURE__ */ new Set();
652
- /** Which vault each override was read from, until its project is known. */
653
- roots = /* @__PURE__ */ new Map();
654
- /**
655
- * An override belongs to the project whose variable it answers.
656
- *
657
- * Nothing in an override says so — they are keyed by variable id alone — so
658
- * the owning project is resolved through the environment that defines the
659
- * variable. Overrides are never synced: they stay in the project's vault on
660
- * this machine, and no other machine sees them.
661
- *
662
- * An override whose variable no longer exists (a deleted environment, a row
663
- * left behind) has no project to belong to. It stays wherever it was read,
664
- * falling back to the key's own directory — never dropped, since the variable
665
- * may simply not be loaded yet.
666
- */
667
- async rootFor(variableId) {
668
- const root = await environmentStore.rootOfVariable(variableId);
669
- if (root) return root;
670
- return this.roots.get(variableId) ?? dirname(await keyedFilePath(FILENAME));
671
- }
672
- /** Where earlier versions kept them: read once, rewritten to their vaults. */
673
- getLegacyFilePath() {
674
- return legacyMachineFilePath(FILENAME);
675
- }
676
- /** Les fichiers qui portent des surcharges : les coffres, et l'emplacement d'avant. */
677
- async sourcePaths() {
678
- const roots = await allVaultRoots();
679
- return [
680
- this.getLegacyFilePath(),
681
- await keyedFilePath(FILENAME),
682
- ...roots.map((root) => join3(root, FILENAME))
683
- ];
684
- }
685
- /**
686
- * Charger, et recharger quand les fichiers ont bougé sous nos pieds.
687
- *
688
- * L'empreinte se prend **après** `ensureProjectsLoaded` : la liste des coffres
689
- * en dépend, et une empreinte prise sur une liste incomplète ne ressemblerait
690
- * à aucune des suivantes — le store se rechargerait à chaque lecture.
691
- */
692
- async load() {
693
- await this.disk.load(async () => {
694
- if (!getEncryptionKey()) return null;
695
- await ensureProjectsLoaded();
696
- const fingerprint = await fingerprintFiles(await this.sourcePaths());
697
- if (this.loaded && fingerprint === this.fingerprint) return null;
698
- const next = await this.readVaults();
699
- return () => {
700
- this.data = next.data;
701
- this.diskEncryptedSlots = next.diskEncryptedSlots;
702
- this.roots = next.roots;
703
- this.unreadableSecrets = next.unreadableSecrets;
704
- this.fingerprint = fingerprint;
705
- this.loaded = true;
706
- };
707
- });
708
- }
709
- /**
710
- * Read every vault, then the places older versions used, into fresh tables
711
- * that `load` swaps in at once.
712
- *
713
- * Variable ids are unique, so merging the files cannot collide; a vault read
714
- * later simply wins over a legacy copy of the same override.
715
- */
716
- async readVaults() {
717
- const data = {};
718
- const diskEncryptedSlots = {};
719
- const roots = /* @__PURE__ */ new Map();
720
- const unreadableSecrets = /* @__PURE__ */ new Set();
721
- for (const path of await this.sourcePaths()) {
722
- let parsed;
723
- try {
724
- parsed = JSON.parse(await readFile2(path, "utf-8"));
725
- } catch {
726
- continue;
727
- }
728
- Object.assign(
729
- diskEncryptedSlots,
730
- extractEncryptedVariableSlots(parsed)
731
- );
732
- const decrypted = decryptVariableSlots(
733
- parsed,
734
- "environment-overrides",
735
- (variableId) => unreadableSecrets.add(variableId),
736
- vaultIdOfRoot(dirname(path))
737
- );
738
- for (const [id, override] of Object.entries(decrypted)) {
739
- data[id] = { value: override.value };
740
- roots.set(id, dirname(path));
741
- }
742
- }
743
- return { data, diskEncryptedSlots, roots, unreadableSecrets };
744
- }
745
- save() {
746
- return this.disk.save(() => this.doSave());
747
- }
748
- /**
749
- * Write each vault that holds — or held — an override.
750
- *
751
- * A vault emptied by this save is still written, as an empty object: an
752
- * override that moved to its project's vault must not survive in the one it
753
- * came from.
754
- */
755
- async doSave() {
756
- const byRoot = /* @__PURE__ */ new Map();
757
- for (const root of this.roots.values()) byRoot.set(root, {});
758
- for (const [variableId, override] of Object.entries(this.data)) {
759
- const root = await this.rootFor(variableId);
760
- this.roots.set(variableId, root);
761
- const bucket = byRoot.get(root) ?? {};
762
- bucket[variableId] = override;
763
- byRoot.set(root, bucket);
764
- }
765
- for (const [root, overrides] of byRoot) {
766
- const encrypted = this.keepUnreadableSecrets(encryptVariableSlots(
767
- overrides,
768
- this.diskEncryptedSlots,
769
- vaultIdOfRoot(root)
770
- ));
771
- Object.assign(
772
- this.diskEncryptedSlots,
773
- extractEncryptedVariableSlots(encrypted)
774
- );
775
- await mkdir(root, { recursive: true });
776
- const path = join3(root, FILENAME);
777
- const tmpPath = path + ".tmp";
778
- await writeFile(tmpPath, JSON.stringify(encrypted, null, 2), "utf-8");
779
- await renameWithRetry(tmpPath, path);
780
- await chmod(path, 384);
781
- }
782
- }
783
- /**
784
- * Rendre au disque le chiffré d'une surcharge illisible, plutôt qu'un blanc.
785
- *
786
- * Pendant du garde-fou du store des environnements : tant que la valeur en
787
- * mémoire est vide, c'est que personne n'a retouché la variable et que son
788
- * chiffré fait toujours foi.
789
- */
790
- keepUnreadableSecrets(overrides) {
791
- if (this.unreadableSecrets.size === 0) return overrides;
792
- const result = { ...overrides };
793
- for (const variableId of this.unreadableSecrets) {
794
- const ciphertext = this.diskEncryptedSlots[variableId];
795
- const override = result[variableId];
796
- if (!ciphertext || !override || override.value.value) continue;
797
- result[variableId] = { ...override, value: { ...override.value, value: ciphertext } };
798
- }
799
- return result;
800
- }
801
- async getOverride(variableId) {
802
- await this.load();
803
- return this.data[variableId] ?? null;
804
- }
805
- async setOverride(variableId, override) {
806
- await this.load();
807
- this.data[variableId] = { value: override.value };
808
- await this.save();
809
- }
810
- async removeOverride(variableId) {
811
- await this.load();
812
- if (!this.data[variableId]) return;
813
- delete this.data[variableId];
814
- this.unreadableSecrets.delete(variableId);
815
- await this.save();
816
- }
817
- async getAll() {
818
- await this.load();
819
- return { ...this.data };
820
- }
821
- async removeOverridesForVariables(variableIds) {
822
- await this.load();
823
- let changed = false;
824
- for (const id of variableIds) {
825
- if (this.data[id]) {
826
- delete this.data[id];
827
- this.unreadableSecrets.delete(id);
828
- changed = true;
829
- }
830
- }
831
- if (changed) await this.save();
832
- }
833
- };
834
- var environmentOverridesStore = new EnvironmentOverridesStore();
835
-
836
- // ../server/src/services/environment-resolver.ts
837
- function interpolateValue(raw, envContext) {
838
- return raw.replace(/\{\{environment\.([^}]+)\}\}/g, (match, key) => {
839
- return envContext[key] !== void 0 ? envContext[key] : match;
840
- });
841
- }
842
- function resolveVariable(variable, envContext) {
843
- const slot = variable.value;
844
- const finalValue = interpolateValue(slot.pre, envContext) + interpolateValue(slot.value, envContext) + interpolateValue(slot.post, envContext);
845
- return { value: finalValue, isSecret: slot.isSecret };
846
- }
847
- function slotReferenceText(variable) {
848
- const slot = variable.value;
849
- return `${slot.pre}
850
- ${slot.value}
851
- ${slot.post}`;
852
- }
853
- 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.`;
854
- function filterEnvForAi(resolved, sharedKeys) {
855
- const secret = new Set(resolved.envSecrets);
856
- const shared = new Set(sharedKeys);
857
- const env2 = {};
858
- const envSecrets = [];
859
- const hiddenKeys = [];
860
- for (const [key, value] of Object.entries(resolved.env)) {
861
- const hidden = secret.has(key) && !shared.has(key);
862
- env2[key] = hidden ? MASKED_VALUE : value;
863
- if (secret.has(key)) envSecrets.push(key);
864
- if (hidden) hiddenKeys.push(key);
865
- }
866
- return {
867
- env: env2,
868
- envSecrets,
869
- hiddenKeys,
870
- ...hiddenKeys.length > 0 ? { hiddenNotice: HIDDEN_VALUES_NOTICE } : {}
871
- };
872
- }
873
- function describeVariables(environments, ctx) {
874
- const details = [];
875
- for (const environment of environments) {
876
- for (const [variableId, variable] of Object.entries(environment.variables ?? {})) {
877
- const slot = variable.value;
878
- const isOverridden = ctx.overriddenVariableIds.has(variableId);
879
- const isSecret = slot.isSecret || ctx.resolvedSecretKeys.has(variable.key);
880
- const readableByAi = ctx.maskedKeys.has(variable.key) ? false : !isSecret || isSharedWithAi(slot);
881
- details.push({
882
- key: variable.key,
883
- inService: ctx.serviceKeys.has(variable.key),
884
- environmentId: environment.id,
885
- environmentName: environment.name,
886
- scope: environment.scope,
887
- ...environment.scope === "service" && environment.reference ? { environmentServiceId: environment.reference } : {},
888
- ...slot.description ? { description: slot.description } : {},
889
- secret: isSecret,
890
- readableByAi,
891
- injectedIntoAi: slot.injectIntoAi,
892
- required: slot.required,
893
- overrideOnly: slot.overrideOnly,
894
- overridden: isOverridden,
895
- ...isOverridden && ctx.sharedValues[variable.key] !== void 0 ? { sharedValue: ctx.sharedValues[variable.key] } : {}
896
- });
897
- }
898
- }
899
- return details.sort((a, b) => a.key.localeCompare(b.key) || a.environmentName.localeCompare(b.environmentName));
900
- }
901
- function withOverriddenValue(variable, override) {
902
- return {
903
- ...variable,
904
- value: {
905
- ...variable.value,
906
- value: override.value,
907
- pre: override.pre,
908
- post: override.post
909
- }
910
- };
911
- }
912
- var EnvironmentResolver = class {
913
- async resolveForService(ctx, opts) {
914
- if (!ctx.activeEnvironmentId) {
915
- return { env: {}, envSecrets: [] };
916
- }
917
- const serviceEnv = await environmentStore.findServiceEnvironment(ctx.serviceId, ctx.activeEnvironmentId);
918
- if (!serviceEnv) {
919
- return this.resolveEnvironment(ctx.activeEnvironmentId, opts);
920
- }
921
- return this.resolveEnvironment(serviceEnv.id, opts);
922
- }
923
- /**
924
- * `applyOverrides: false` rend ce que l'équipe partage, sans ce que ce poste
925
- * a mis par-dessus : c'est la réponse à « qu'est-ce que ma surcharge
926
- * remplace ». Le défaut reste `true` — tout le reste du produit veut la
927
- * valeur avec laquelle le service tourne réellement.
928
- */
929
- async resolveEnvironment(environmentId, opts) {
930
- const visited = /* @__PURE__ */ new Set();
931
- const result = await this.resolveDag(environmentId, visited);
932
- const overridesByKey = opts?.applyOverrides === false ? {} : await this.resolveOverridesByKey(environmentId);
933
- for (const [varId, variable] of Object.entries(result.variables)) {
934
- const override = overridesByKey[variable.key];
935
- if (override) {
936
- result.variables[varId] = withOverriddenValue(variable, override.variable.value);
937
- }
938
- }
939
- const rawEnv = {};
940
- for (const [_varId, variable] of Object.entries(result.variables)) {
941
- const slot = variable.value;
942
- rawEnv[variable.key] = slot.pre + slot.value + slot.post;
943
- }
944
- const { ctx: interpolationCtx, secretKeys: ctxSecretKeys } = await this.buildInterpolationContext(environmentId, rawEnv, result.parentEnv, result.parentSecretKeys, result.parentKeySources);
945
- const secretKeys = this.collectSecretKeys(result.variables, ctxSecretKeys);
946
- const env2 = {};
947
- const envSecrets = [];
948
- for (const [_varId, variable] of Object.entries(result.variables)) {
949
- const { value, isSecret } = resolveVariable(variable, interpolationCtx);
950
- env2[variable.key] = value;
951
- if (isSecret || secretKeys.has(variable.key)) {
952
- envSecrets.push(variable.key);
953
- }
954
- }
955
- return { env: env2, envSecrets };
956
- }
957
- /**
958
- * Resolve an environment (with inheritance) and return only the variables
959
- * flagged `injectIntoAi`, as a flat KEY→value dict ready to be injected into
960
- * the process environment of an AI agent spawn.
961
- */
962
- async resolveAiInjectedEnv(environmentId) {
963
- if (!environmentId) return {};
964
- const visited = /* @__PURE__ */ new Set();
965
- const result = await this.resolveDag(environmentId, visited);
966
- const overridesByKey = await this.resolveOverridesByKey(environmentId);
967
- for (const [varId, variable] of Object.entries(result.variables)) {
968
- const override = overridesByKey[variable.key];
969
- if (override) {
970
- result.variables[varId] = withOverriddenValue(variable, override.variable.value);
971
- }
972
- }
973
- const rawEnv = {};
974
- for (const variable of Object.values(result.variables)) {
975
- const slot = variable.value;
976
- rawEnv[variable.key] = slot.pre + slot.value + slot.post;
977
- }
978
- const { ctx: interpolationCtx } = await this.buildInterpolationContext(
979
- environmentId,
980
- rawEnv,
981
- result.parentEnv,
982
- result.parentSecretKeys,
983
- result.parentKeySources
984
- );
985
- const injected = {};
986
- for (const variable of Object.values(result.variables)) {
987
- if (!variable.value.injectIntoAi) continue;
988
- const { value } = resolveVariable(variable, interpolationCtx);
989
- injected[variable.key] = value;
990
- }
991
- return injected;
992
- }
993
- /**
994
- * Les variables qu'il faut remplir avant qu'un service de cet environnement
995
- * puisse démarrer.
996
- *
997
- * Deux motifs, et ils ne se mesurent pas au même endroit :
998
- *
999
- * - `required` porte sur la valeur **finale**, interpolation et surcharge
1000
- * comprises : une variable obligatoire dont la valeur ne tient qu'à une
1001
- * référence morte est aussi vide qu'une case blanche.
1002
- * - `overrideOnly` porte sur l'**existence d'une surcharge machine**. Elle
1003
- * n'a par construction pas de valeur partagée ; ce qui manque n'est pas un
1004
- * contenu, c'est la réponse de ce poste-ci.
1005
- *
1006
- * Les drapeaux se lisent sur la définition, jamais sur le slot résolu : une
1007
- * surcharge remplace le slot entier et ne porte aucun drapeau — les y lire
1008
- * rendrait toute variable surchargée silencieusement facultative.
1009
- */
1010
- async findMissingVariables(environmentId) {
1011
- if (!environmentId) return [];
1012
- const [{ env: env2 }, definitions, overridesByKey] = await Promise.all([
1013
- this.resolveEnvironment(environmentId),
1014
- this.resolveDag(environmentId, /* @__PURE__ */ new Set()),
1015
- this.resolveOverridesByKey(environmentId)
1016
- ]);
1017
- const missing = [];
1018
- for (const [variableId, variable] of Object.entries(definitions.variables)) {
1019
- const slot = variable.value;
1020
- const source = definitions.sources[variableId];
1021
- const base = {
1022
- variableId,
1023
- key: variable.key,
1024
- description: slot.description,
1025
- isSecret: slot.isSecret,
1026
- environmentId: source?.environmentId ?? environmentId,
1027
- environmentName: source?.environmentName ?? ""
1028
- };
1029
- if (slot.overrideOnly && !overridesByKey[variable.key]) {
1030
- missing.push({ ...base, reason: "overrideOnly" });
1031
- continue;
1032
- }
1033
- if (slot.required && (env2[variable.key] ?? "") === "") {
1034
- missing.push({ ...base, reason: "required" });
1035
- }
1036
- }
1037
- return missing;
1038
- }
1039
- /**
1040
- * Tout ce qu'il reste à remplir sous l'environnement courant.
1041
- *
1042
- * Deux périmètres, et il faut les deux :
1043
- *
1044
- * 1. **L'environnement courant lui-même**, héritage compris —
1045
- * `findMissingVariables` remonte ses `parentIds`. C'est là que vivent les
1046
- * variables globales du projet, et c'est la première branche balayée.
1047
- * 2. **Les environnements de service qui s'activent avec lui**
1048
- * (`activateOn`). Ceux-là ne sont pas des ancêtres du global : ils en
1049
- * héritent, dans l'autre sens. Ne pas y descendre revenait à ne rien
1050
- * réclamer d'un service jusqu'à ce qu'on essaie de le démarrer.
1051
- *
1052
- * Dédoublonné par identifiant de variable : une variable du global remonte
1053
- * dans chaque branche de service qui en hérite, et l'assistant ne doit pas
1054
- * demander deux fois la même chose.
1055
- */
1056
- async findMissingForActiveEnvironment(globalEnvId) {
1057
- if (!globalEnvId) return [];
1058
- const serviceEnvs = await environmentStore.listByActivateOn(globalEnvId);
1059
- const branches = await Promise.all([
1060
- this.findMissingVariables(globalEnvId),
1061
- ...serviceEnvs.map((env2) => this.findMissingVariables(env2.id))
1062
- ]);
1063
- const seen = /* @__PURE__ */ new Set();
1064
- const missing = [];
1065
- for (const branch of branches) {
1066
- for (const item of branch) {
1067
- if (seen.has(item.variableId)) continue;
1068
- seen.add(item.variableId);
1069
- missing.push(item);
1070
- }
1071
- }
1072
- return missing;
1073
- }
1074
- /**
1075
- * Les clés que leur auteur a partagées avec les IA, héritage compris.
1076
- *
1077
- * Le partage se lit sur la **définition**, et seulement là : une surcharge
1078
- * machine ne dit que la valeur de ce poste (`withOverriddenValue`). Il n'y a
1079
- * donc qu'un endroit où ce drapeau existe, et rien à réconcilier.
1080
- *
1081
- * Des clés, pas des valeurs : la valeur rendue reste celle que le service
1082
- * reçoit, surcharges et interpolation comprises.
1083
- */
1084
- async resolveAiSharedKeys(environmentId) {
1085
- if (!environmentId) return /* @__PURE__ */ new Set();
1086
- const result = await this.resolveDag(environmentId, /* @__PURE__ */ new Set());
1087
- const keys = /* @__PURE__ */ new Set();
1088
- for (const variable of Object.values(result.variables)) {
1089
- if (isSharedWithAi(variable.value)) keys.add(variable.key);
1090
- }
1091
- return keys;
1092
- }
1093
- /**
1094
- * Idem, pour ce qu'un service verra réellement : son environnement dédié
1095
- * quand il en a un, l'environnement global sinon — la règle de sélection de
1096
- * `resolveForService`.
1097
- */
1098
- async resolveAiSharedKeysForService(ctx) {
1099
- if (!ctx.activeEnvironmentId) return /* @__PURE__ */ new Set();
1100
- const serviceEnv = await environmentStore.findServiceEnvironment(ctx.serviceId, ctx.activeEnvironmentId);
1101
- return this.resolveAiSharedKeys(serviceEnv ? serviceEnv.id : ctx.activeEnvironmentId);
1102
- }
1103
- /**
1104
- * Idem, pour ce qu'un service verra réellement : son environnement dédié
1105
- * quand il en a un, l'environnement global sinon. Miroir de
1106
- * `resolveForService`, dont il partage la règle de sélection.
1107
- */
1108
- async findMissingForService(ctx) {
1109
- if (!ctx.activeEnvironmentId) return [];
1110
- const serviceEnv = await environmentStore.findServiceEnvironment(ctx.serviceId, ctx.activeEnvironmentId);
1111
- return this.findMissingVariables(serviceEnv ? serviceEnv.id : ctx.activeEnvironmentId);
1112
- }
1113
- async resolveEnvironmentWithSources(environmentId) {
1114
- const visited = /* @__PURE__ */ new Set();
1115
- const result = await this.resolveDag(environmentId, visited);
1116
- const overridesByKey = await this.resolveOverridesByKey(environmentId);
1117
- for (const [varId, variable] of Object.entries(result.variables)) {
1118
- const override = overridesByKey[variable.key];
1119
- if (override) {
1120
- result.variables[varId] = withOverriddenValue(variable, override.variable.value);
1121
- result.sources[varId] = {
1122
- ...result.sources[varId],
1123
- overridden: true,
1124
- ...override.source ? { overriddenBy: override.source } : {}
1125
- };
1126
- }
1127
- }
1128
- const rawEnv = {};
1129
- for (const [_varId, variable] of Object.entries(result.variables)) {
1130
- const slot = variable.value;
1131
- rawEnv[variable.key] = slot.pre + slot.value + slot.post;
1132
- }
1133
- const { ctx: interpolationCtx, secretKeys: ctxSecretKeys, keySources } = await this.buildInterpolationContext(environmentId, rawEnv, result.parentEnv, result.parentSecretKeys, result.parentKeySources);
1134
- const secretKeys = this.collectSecretKeys(result.variables, ctxSecretKeys);
1135
- const env2 = {};
1136
- const envSecrets = [];
1137
- for (const [_varId, variable] of Object.entries(result.variables)) {
1138
- const { value, isSecret } = resolveVariable(variable, interpolationCtx);
1139
- env2[variable.key] = value;
1140
- if (isSecret || secretKeys.has(variable.key)) {
1141
- envSecrets.push(variable.key);
1142
- }
1143
- }
1144
- const refPattern = /^\{\{environment\.([^}]+)\}\}$/;
1145
- for (const [varId, variable] of Object.entries(result.variables)) {
1146
- const match = variable.value.value.match(refPattern);
1147
- if (match) {
1148
- const refSource = keySources[match[1]];
1149
- if (refSource && result.sources[varId]?.environmentId !== refSource.environmentId) {
1150
- result.sources[varId] = {
1151
- ...result.sources[varId],
1152
- resolvedFrom: refSource
1153
- };
1154
- }
1155
- }
1156
- }
1157
- return {
1158
- variables: result.variables,
1159
- env: env2,
1160
- envSecrets,
1161
- sources: result.sources
1162
- };
1163
- }
1164
- /** Check if a raw value string interpolates any key that is secret in the context */
1165
- /**
1166
- * Toutes les clés dont la valeur est, directement ou par interpolation, un
1167
- * secret.
1168
- *
1169
- * Le contexte d'interpolation ne connaît que les secrets *hérités* : les
1170
- * secrets définis dans le même environnement n'y figurent pas, si bien qu'un
1171
- * `AUTH='Bearer {{environment.TOKEN}}'` voisin de son propre `TOKEN` secret
1172
- * ressortait non marqué — et donc non masqué par toute la chaîne en aval.
1173
- *
1174
- * On part des secrets hérités plus ceux déclarés ici, puis on propage
1175
- * jusqu'au point fixe : une valeur qui référence une clé secrète devient
1176
- * secrète à son tour, y compris en chaîne (A secret → B cite A → C cite B).
1177
- */
1178
- collectSecretKeys(variables, parentSecretKeys) {
1179
- const secretKeys = new Set(parentSecretKeys);
1180
- for (const variable of Object.values(variables)) {
1181
- if (variable.value.isSecret) secretKeys.add(variable.key);
1182
- }
1183
- let changed = true;
1184
- while (changed) {
1185
- changed = false;
1186
- for (const variable of Object.values(variables)) {
1187
- if (secretKeys.has(variable.key)) continue;
1188
- if (this.interpolatesSecretKey(slotReferenceText(variable), secretKeys)) {
1189
- secretKeys.add(variable.key);
1190
- changed = true;
1191
- }
1192
- }
1193
- }
1194
- return secretKeys;
1195
- }
1196
- interpolatesSecretKey(rawValue, ctxSecretKeys) {
1197
- const refs = [...rawValue.matchAll(/\{\{environment\.([^}]+)\}\}/g)].map((m) => m[1]);
1198
- return refs.some((ref) => ctxSecretKeys.has(ref));
1199
- }
1200
- /**
1201
- * Build the interpolation context for an environment.
1202
- *
1203
- * For inheriting envs (has parents): start from parent concrete values,
1204
- * then overlay with current concrete values. This ensures that
1205
- * {{environment.KEY}} resolves against the parent's value when the child
1206
- * redefines KEY as an interpolation reference.
1207
- *
1208
- * For isolated service envs (scope=service, activateOn set, no parents):
1209
- * the global env is resolved and used as fallback.
1210
- */
1211
- async buildInterpolationContext(environmentId, rawEnv, parentEnv, parentSecretKeys, parentKeySources) {
1212
- const environment = await environmentStore.get(environmentId);
1213
- if (environment && environment.scope === "service" && environment.activateOn && (!environment.parentIds || environment.parentIds.length === 0)) {
1214
- const globalResolved = await this.resolveEnvironmentWithSources(environment.activateOn);
1215
- const secretKeys = new Set(globalResolved.envSecrets);
1216
- const keySources = {};
1217
- for (const [varId, variable] of Object.entries(globalResolved.variables)) {
1218
- const source = globalResolved.sources[varId];
1219
- if (source) {
1220
- keySources[variable.key] = { environmentId: source.environmentId, environmentName: source.environmentName };
1221
- }
1222
- }
1223
- const ctx2 = { ...globalResolved.env };
1224
- for (const [key, value] of Object.entries(rawEnv)) {
1225
- if (!/\{\{environment\.[^}]+\}\}/.test(value)) {
1226
- ctx2[key] = value;
1227
- }
1228
- }
1229
- return { ctx: ctx2, secretKeys, keySources };
1230
- }
1231
- const ctx = { ...parentEnv };
1232
- for (const [key, value] of Object.entries(rawEnv)) {
1233
- if (!/\{\{environment\.[^}]+\}\}/.test(value)) {
1234
- ctx[key] = value;
1235
- }
1236
- }
1237
- return { ctx, secretKeys: parentSecretKeys, keySources: parentKeySources };
1238
- }
1239
- /**
1240
- * For service-scoped envs, compute effective parentIds by also including
1241
- * the service-scoped envs of ancestor global environments.
1242
- *
1243
- * Example: "Server (test)" has parentIds=[test-global]. test-global has
1244
- * parentIds=[Local-global]. This method adds "Server (Local)" to the
1245
- * effective parents so service-specific variables from ancestor envs
1246
- * are inherited correctly.
1247
- */
1248
- async getEffectiveParentIds(environment) {
1249
- if (environment.scope !== "service" || !environment.reference || !environment.activateOn) {
1250
- return environment.parentIds;
1251
- }
1252
- const additionalIds = [];
1253
- for (const parentId of environment.parentIds) {
1254
- const parentEnv = await environmentStore.get(parentId);
1255
- if (!parentEnv || parentEnv.scope !== "global") continue;
1256
- for (const grandparentGlobalId of parentEnv.parentIds) {
1257
- const ancestorServiceEnv = await environmentStore.findServiceEnvironment(environment.reference, grandparentGlobalId);
1258
- if (ancestorServiceEnv && !environment.parentIds.includes(ancestorServiceEnv.id)) {
1259
- additionalIds.push(ancestorServiceEnv.id);
1260
- }
1261
- }
1262
- }
1263
- if (additionalIds.length === 0) return environment.parentIds;
1264
- return [...environment.parentIds, ...additionalIds];
1265
- }
1266
- /**
1267
- * Project an environment's variables onto their overrides: keep only the
1268
- * variables that have one, and swap in the override's value slot. The key
1269
- * always comes from the definition.
1270
- *
1271
- * Feeding this projection back through `resolveDag` resolves the override
1272
- * chain with the exact same inheritance and dedup-by-key rules as the value
1273
- * chain — so an override set on a parent env is no longer swallowed when a
1274
- * child (or a service env) redeclares the same key.
1275
- */
1276
- projectOverrides(variables, overrides) {
1277
- const projected = {};
1278
- for (const [varId, variable] of Object.entries(variables)) {
1279
- const override = overrides[varId];
1280
- if (override) {
1281
- projected[varId] = { key: variable.key, value: override.value };
1282
- }
1283
- }
1284
- return projected;
1285
- }
1286
- /**
1287
- * Resolve the override chain independently of the value chain and return the
1288
- * winning override per variable key (empty when nothing is overridden).
1289
- */
1290
- async resolveOverridesByKey(environmentId) {
1291
- const allOverrides = await environmentOverridesStore.getAll();
1292
- if (Object.keys(allOverrides).length === 0) return {};
1293
- const result = await this.resolveDag(
1294
- environmentId,
1295
- /* @__PURE__ */ new Set(),
1296
- (variables) => this.projectOverrides(variables, allOverrides)
1297
- );
1298
- const byKey = {};
1299
- for (const [varId, variable] of Object.entries(result.variables)) {
1300
- const source = result.sources[varId];
1301
- byKey[variable.key] = {
1302
- variable,
1303
- ...source ? { source: { environmentId: source.environmentId, environmentName: source.environmentName } } : {}
1304
- };
1305
- }
1306
- return byKey;
1307
- }
1308
- async resolveDag(environmentId, visited, project) {
1309
- if (visited.has(environmentId)) {
1310
- throw new Error(`Circular dependency detected in environment inheritance for ${environmentId}`);
1311
- }
1312
- const environment = await environmentStore.get(environmentId);
1313
- if (!environment) {
1314
- throw new Error(`Environment "${environmentId}" not found`);
1315
- }
1316
- visited.add(environmentId);
1317
- const effectiveParentIds = await this.getEffectiveParentIds(environment);
1318
- const parentResults = [];
1319
- for (const parentId of effectiveParentIds) {
1320
- const parentVisited = new Set(visited);
1321
- const parentResult = await this.resolveDag(parentId, parentVisited, project);
1322
- parentResults.push(parentResult);
1323
- }
1324
- const mergedVariables = {};
1325
- const sources = {};
1326
- for (const result of parentResults) {
1327
- for (const [varId, variable] of Object.entries(result.variables)) {
1328
- for (const [existingId, existingVar] of Object.entries(mergedVariables)) {
1329
- if (existingVar.key === variable.key && existingId !== varId) {
1330
- delete mergedVariables[existingId];
1331
- delete sources[existingId];
1332
- }
1333
- }
1334
- mergedVariables[varId] = variable;
1335
- sources[varId] = result.sources[varId] ?? sources[varId];
1336
- }
1337
- }
1338
- const parentEnv = {};
1339
- const parentSecretKeys = /* @__PURE__ */ new Set();
1340
- const parentKeySources = {};
1341
- for (const [varId, variable] of Object.entries(mergedVariables)) {
1342
- const slot = variable.value;
1343
- parentEnv[variable.key] = slot.pre + slot.value + slot.post;
1344
- if (slot.isSecret) {
1345
- parentSecretKeys.add(variable.key);
1346
- }
1347
- const source = sources[varId];
1348
- if (source) {
1349
- parentKeySources[variable.key] = { environmentId: source.environmentId, environmentName: source.environmentName };
1350
- }
1351
- }
1352
- const isInterpRef = (v) => /\{\{environment\.[^}]+\}\}/.test(v);
1353
- for (const result of parentResults) {
1354
- for (const [key, value] of Object.entries(result.parentEnv)) {
1355
- const existingIsRef = key in parentEnv && isInterpRef(parentEnv[key]);
1356
- const newIsConcrete = !isInterpRef(value);
1357
- if (!(key in parentEnv) || existingIsRef && newIsConcrete) {
1358
- parentEnv[key] = value;
1359
- }
1360
- }
1361
- for (const key of result.parentSecretKeys) {
1362
- parentSecretKeys.add(key);
1363
- }
1364
- for (const [key, source] of Object.entries(result.parentKeySources)) {
1365
- if (!(key in parentKeySources)) {
1366
- parentKeySources[key] = source;
1367
- }
1368
- }
1369
- }
1370
- for (const key of this.collectSecretKeys(mergedVariables, parentSecretKeys)) {
1371
- parentSecretKeys.add(key);
1372
- }
1373
- const ownVariables = project ? project(environment.variables) : environment.variables;
1374
- for (const [varId, variable] of Object.entries(ownVariables)) {
1375
- let wasInherited = varId in mergedVariables;
1376
- for (const [existingId, existingVar] of Object.entries(mergedVariables)) {
1377
- if (existingVar.key === variable.key && existingId !== varId) {
1378
- delete mergedVariables[existingId];
1379
- delete sources[existingId];
1380
- wasInherited = true;
1381
- }
1382
- }
1383
- mergedVariables[varId] = variable;
1384
- sources[varId] = {
1385
- environmentId: environment.id,
1386
- environmentName: environment.name,
1387
- overridden: wasInherited
1388
- };
1389
- }
1390
- return {
1391
- variables: mergedVariables,
1392
- sources,
1393
- parentEnv,
1394
- parentSecretKeys,
1395
- parentKeySources
1396
- };
1397
- }
1398
- };
1399
- var environmentResolver = new EnvironmentResolver();
1400
-
1401
634
  // ../server/src/services/llms-txt-generator.ts
1402
635
  function generateAdminLlmsTxt(services, jwt, project) {
1403
636
  const lines = [];
@@ -1611,7 +844,7 @@ import { z as z3 } from "zod";
1611
844
  import { TRPCError as TRPCError2 } from "@trpc/server";
1612
845
 
1613
846
  // ../server/src/services/audit-service.ts
1614
- import { appendFile, readFile as readFile3, stat, rename, unlink, readdir as readdir2, mkdir as mkdir2 } from "fs/promises";
847
+ import { appendFile, readFile as readFile2, stat, rename, unlink, readdir as readdir2, mkdir } from "fs/promises";
1615
848
  var AUDIT_FILENAME = "audit.log";
1616
849
  var MAX_FILE_SIZE = 10 * 1024 * 1024;
1617
850
  var MAX_AGE_DAYS = 30;
@@ -1631,7 +864,7 @@ var AuditService = class {
1631
864
  const line = JSON.stringify(record) + "\n";
1632
865
  const filepath = this.getFilePath();
1633
866
  try {
1634
- await mkdir2(machineRoot(), { recursive: true });
867
+ await mkdir(machineRoot(), { recursive: true });
1635
868
  await appendFile(filepath, line, "utf-8");
1636
869
  await this.rotateIfNeeded();
1637
870
  } catch (err) {
@@ -1644,7 +877,7 @@ var AuditService = class {
1644
877
  const filepath = this.getFilePath();
1645
878
  let lines;
1646
879
  try {
1647
- const content = await readFile3(filepath, "utf-8");
880
+ const content = await readFile2(filepath, "utf-8");
1648
881
  lines = content.trim().split("\n").filter(Boolean);
1649
882
  } catch {
1650
883
  return { entries: [], total: 0 };
@@ -2485,8 +1718,8 @@ var ScenarioStore = class {
2485
1718
  var scenarioStore = new ScenarioStore();
2486
1719
 
2487
1720
  // ../server/src/services/scenario-run-store.ts
2488
- import { mkdir as mkdir3, rm, readFile as readFile4 } from "fs/promises";
2489
- import { join as join4 } from "path";
1721
+ import { mkdir as mkdir2, rm, readFile as readFile3 } from "fs/promises";
1722
+ import { join as join3 } from "path";
2490
1723
  function sanitizeSlug(value) {
2491
1724
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "member";
2492
1725
  }
@@ -2502,7 +1735,7 @@ var ScenarioRunStore = class _ScenarioRunStore {
2502
1735
  }
2503
1736
  async initializeConversationWorkspace(conversationId, scenario, userInput) {
2504
1737
  const dir = this.getRunDir(conversationId);
2505
- await mkdir3(dir, { recursive: true });
1738
+ await mkdir2(dir, { recursive: true });
2506
1739
  await this.saveScenarioSnapshot(conversationId, scenario);
2507
1740
  const nodesById = new Map(scenario.nodes.map((p) => [p.id, p]));
2508
1741
  const teamMap = new Map(scenario.groups.map((t2) => [t2.id, t2.name]));
@@ -2527,8 +1760,8 @@ var ScenarioRunStore = class _ScenarioRunStore {
2527
1760
  "- Use read-only references from linked upstream files as context.",
2528
1761
  ""
2529
1762
  ].join("\n");
2530
- await atomicWriteSecure(join4(dir, "README.md"), overview);
2531
- await atomicWriteSecure(join4(dir, "USER_REQUEST.md"), `# User request
1763
+ await atomicWriteSecure(join3(dir, "README.md"), overview);
1764
+ await atomicWriteSecure(join3(dir, "USER_REQUEST.md"), `# User request
2532
1765
 
2533
1766
  ${userInput.trim() || "(empty)"}
2534
1767
  `);
@@ -2554,7 +1787,7 @@ ${userInput.trim() || "(empty)"}
2554
1787
  "- Waiting for assignment.",
2555
1788
  ""
2556
1789
  ].join("\n");
2557
- await atomicWriteSecure(join4(dir, fileName), content);
1790
+ await atomicWriteSecure(join3(dir, fileName), content);
2558
1791
  }
2559
1792
  }
2560
1793
  async deleteRunDir(conversationId) {
@@ -2614,7 +1847,7 @@ ${userInput.trim() || "(empty)"}
2614
1847
  if (!runState) return;
2615
1848
  try {
2616
1849
  const dir = this.getRunDir(conversationId);
2617
- await mkdir3(dir, { recursive: true });
1850
+ await mkdir2(dir, { recursive: true });
2618
1851
  const data = {
2619
1852
  nodeStates: { ...runState.nodeStates },
2620
1853
  nodeOutputs: { ...runState.nodeOutputs },
@@ -2622,18 +1855,18 @@ ${userInput.trim() || "(empty)"}
2622
1855
  ...Object.keys(runState.nodeAgentIds).length > 0 ? { nodeAgentIds: { ...runState.nodeAgentIds } } : {},
2623
1856
  ...Object.keys(runState.waitingInputs).length > 0 ? { waitingInputs: { ...runState.waitingInputs } } : {}
2624
1857
  };
2625
- await atomicWriteJsonSecure(join4(dir, "run-state.json"), data);
1858
+ await atomicWriteJsonSecure(join3(dir, "run-state.json"), data);
2626
1859
  } catch {
2627
1860
  }
2628
1861
  }
2629
1862
  async loadPersistedRunState(conversationId) {
2630
1863
  const dir = this.getRunDir(conversationId);
2631
- return readJsonFileSafe(join4(dir, "run-state.json"));
1864
+ return readJsonFileSafe(join3(dir, "run-state.json"));
2632
1865
  }
2633
1866
  async readUserRequest(conversationId) {
2634
1867
  try {
2635
1868
  const dir = this.getRunDir(conversationId);
2636
- const content = await readFile4(join4(dir, "USER_REQUEST.md"), "utf-8");
1869
+ const content = await readFile3(join3(dir, "USER_REQUEST.md"), "utf-8");
2637
1870
  return content.replace(/^#\s*User request\s*\n+/, "").trim() || null;
2638
1871
  } catch {
2639
1872
  return null;
@@ -2642,23 +1875,23 @@ ${userInput.trim() || "(empty)"}
2642
1875
  async readReadme(conversationId) {
2643
1876
  try {
2644
1877
  const dir = this.getRunDir(conversationId);
2645
- return await readFile4(join4(dir, "README.md"), "utf-8");
1878
+ return await readFile3(join3(dir, "README.md"), "utf-8");
2646
1879
  } catch {
2647
1880
  return null;
2648
1881
  }
2649
1882
  }
2650
1883
  async saveScenarioSnapshot(conversationId, scenario) {
2651
1884
  const dir = this.getRunDir(conversationId);
2652
- await mkdir3(dir, { recursive: true });
1885
+ await mkdir2(dir, { recursive: true });
2653
1886
  await atomicWriteJsonSecure(
2654
- join4(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE),
1887
+ join3(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE),
2655
1888
  scenario,
2656
1889
  true
2657
1890
  );
2658
1891
  }
2659
1892
  async loadScenarioSnapshot(conversationId) {
2660
1893
  const dir = this.getRunDir(conversationId);
2661
- return readJsonFileSafe(join4(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE));
1894
+ return readJsonFileSafe(join3(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE));
2662
1895
  }
2663
1896
  };
2664
1897
  var scenarioRunStore = new ScenarioRunStore();
@@ -2713,9 +1946,9 @@ async function vaultOfProject(projectId) {
2713
1946
 
2714
1947
  // ../server/src/services/image-storage.service.ts
2715
1948
  import { randomUUID as randomUUID3 } from "crypto";
2716
- import { mkdir as mkdir4, writeFile as writeFile2, readFile as readFile5, unlink as unlink2, readdir as readdir3 } from "fs/promises";
1949
+ import { mkdir as mkdir3, writeFile, readFile as readFile4, unlink as unlink2, readdir as readdir3 } from "fs/promises";
2717
1950
  import { existsSync as existsSync2 } from "fs";
2718
- import { join as join5, extname, resolve, sep } from "path";
1951
+ import { join as join4, extname, resolve, sep } from "path";
2719
1952
  var ALLOWED_MIME_TYPES = /* @__PURE__ */ new Set([
2720
1953
  "image/png",
2721
1954
  "image/jpeg",
@@ -2732,7 +1965,7 @@ var ImageStorageService = class {
2732
1965
  this.dir = dir;
2733
1966
  }
2734
1967
  async ensureTmpDir() {
2735
- await mkdir4(this.dir, { recursive: true, mode: 448 });
1968
+ await mkdir3(this.dir, { recursive: true, mode: 448 });
2736
1969
  }
2737
1970
  async saveImage(buffer, originalName, mimeType, contextId) {
2738
1971
  if (!ALLOWED_MIME_TYPES.has(mimeType)) {
@@ -2752,15 +1985,15 @@ var ImageStorageService = class {
2752
1985
  const imageId = randomUUID3();
2753
1986
  const safeExt = ext.replace(/[^a-z.]/g, "");
2754
1987
  const fileName = `${imageId}${safeExt}`;
2755
- const filePath = join5(this.dir, fileName);
1988
+ const filePath = join4(this.dir, fileName);
2756
1989
  const resolvedPath = resolve(filePath);
2757
1990
  const resolvedDir = resolve(this.dir);
2758
1991
  if (resolvedPath !== resolvedDir && !resolvedPath.startsWith(resolvedDir + sep)) {
2759
1992
  throw new Error("Invalid file path");
2760
1993
  }
2761
- await writeFile2(filePath, buffer, { mode: 384 });
1994
+ await writeFile(filePath, buffer, { mode: 384 });
2762
1995
  const meta = { imageId, filePath, mimeType, originalName, createdAt: Date.now(), contextId };
2763
- await writeFile2(`${filePath}.meta.json`, JSON.stringify(meta), { encoding: "utf-8", mode: 384 });
1996
+ await writeFile(`${filePath}.meta.json`, JSON.stringify(meta), { encoding: "utf-8", mode: 384 });
2764
1997
  return meta;
2765
1998
  }
2766
1999
  /**
@@ -2778,7 +2011,7 @@ var ImageStorageService = class {
2778
2011
  const files = await readdir3(this.dir);
2779
2012
  const metaFile = files.find((f) => f.startsWith(imageId) && f.endsWith(".meta.json"));
2780
2013
  if (!metaFile) return null;
2781
- const raw = JSON.parse(await readFile5(join5(this.dir, metaFile), "utf-8"));
2014
+ const raw = JSON.parse(await readFile4(join4(this.dir, metaFile), "utf-8"));
2782
2015
  if (expectedContextId !== void 0 && raw.contextId !== expectedContextId) return null;
2783
2016
  if (!existsSync2(raw.filePath)) return null;
2784
2017
  return { filePath: raw.filePath, mimeType: raw.mimeType, contextId: raw.contextId };
@@ -2804,7 +2037,7 @@ var ImageStorageService = class {
2804
2037
  await Promise.all(
2805
2038
  files.filter((f) => f.endsWith(".meta.json")).map(async (file) => {
2806
2039
  try {
2807
- const raw = JSON.parse(await readFile5(join5(this.dir, file), "utf-8"));
2040
+ const raw = JSON.parse(await readFile4(join4(this.dir, file), "utf-8"));
2808
2041
  if (raw.contextId === contextId) {
2809
2042
  await unlink2(raw.filePath).catch(() => {
2810
2043
  });
@@ -2819,7 +2052,7 @@ var ImageStorageService = class {
2819
2052
  async readImageAsBase64(imageId, expectedContextId) {
2820
2053
  const result = await this.getImagePath(imageId, expectedContextId);
2821
2054
  if (!result) return null;
2822
- const buffer = await readFile5(result.filePath);
2055
+ const buffer = await readFile4(result.filePath);
2823
2056
  return { base64: buffer.toString("base64"), mimeType: result.mimeType };
2824
2057
  }
2825
2058
  async listExpiredImages(ttlMs = IMAGE_TTL_MS) {
@@ -2834,7 +2067,7 @@ var ImageStorageService = class {
2834
2067
  for (const file of files) {
2835
2068
  if (!file.endsWith(".meta.json")) continue;
2836
2069
  try {
2837
- const raw = JSON.parse(await readFile5(join5(this.dir, file), "utf-8"));
2070
+ const raw = JSON.parse(await readFile4(join4(this.dir, file), "utf-8"));
2838
2071
  if (now - raw.createdAt > ttlMs) expired.push(raw);
2839
2072
  } catch {
2840
2073
  }
@@ -2903,10 +2136,10 @@ import { basename } from "path";
2903
2136
  import { TRPCError as TRPCError3 } from "@trpc/server";
2904
2137
 
2905
2138
  // ../server/src/services/cloud-session-store.ts
2906
- import { readFile as readFile6, writeFile as writeFile3, mkdir as mkdir5, chmod as chmod2, unlink as unlink3 } from "fs/promises";
2907
- import { dirname as dirname2 } from "path";
2139
+ import { readFile as readFile5, writeFile as writeFile2, mkdir as mkdir4, chmod, unlink as unlink3 } from "fs/promises";
2140
+ import { dirname } from "path";
2908
2141
  import { randomBytes as randomBytes2, createHash, timingSafeEqual as timingSafeEqual2 } from "crypto";
2909
- var FILENAME2 = "cloud-session.json";
2142
+ var FILENAME = "cloud-session.json";
2910
2143
  var STATE_TTL_MS = 5 * 60 * 1e3;
2911
2144
  function accountIdFor(cloudUrl, userId) {
2912
2145
  return createHash("sha256").update(`${cloudUrl.replace(/\/+$/, "")}|${userId}`).digest("hex").slice(0, 32);
@@ -2921,11 +2154,11 @@ var CloudSessionStore = class {
2921
2154
  * root, but a launch directory owning its own key keeps both together.
2922
2155
  */
2923
2156
  getFilePath() {
2924
- return keyedFilePath(FILENAME2);
2157
+ return keyedFilePath(FILENAME);
2925
2158
  }
2926
2159
  /** Where older versions kept it — read only, so an older Runeya keeps working. */
2927
2160
  getLegacyFilePath() {
2928
- return legacyMachineFilePath(FILENAME2);
2161
+ return legacyMachineFilePath(FILENAME);
2929
2162
  }
2930
2163
  /**
2931
2164
  * Read the machine file, falling back to the launch directory of old.
@@ -2935,9 +2168,9 @@ var CloudSessionStore = class {
2935
2168
  */
2936
2169
  async readFromDisk() {
2937
2170
  try {
2938
- return await readFile6(await this.getFilePath(), "utf-8");
2171
+ return await readFile5(await this.getFilePath(), "utf-8");
2939
2172
  } catch {
2940
- return readFile6(this.getLegacyFilePath(), "utf-8");
2173
+ return readFile5(this.getLegacyFilePath(), "utf-8");
2941
2174
  }
2942
2175
  }
2943
2176
  /**
@@ -3011,12 +2244,12 @@ var CloudSessionStore = class {
3011
2244
  }
3012
2245
  async save() {
3013
2246
  const filePath = await this.getFilePath();
3014
- await mkdir5(dirname2(filePath), { recursive: true });
2247
+ await mkdir4(dirname(filePath), { recursive: true });
3015
2248
  const payload = { version: 2, accounts: this.accounts };
3016
2249
  const tmpPath = filePath + ".tmp";
3017
- await writeFile3(tmpPath, JSON.stringify(payload, null, 2), "utf-8");
2250
+ await writeFile2(tmpPath, JSON.stringify(payload, null, 2), "utf-8");
3018
2251
  await renameWithRetry(tmpPath, filePath);
3019
- await chmod2(filePath, 384);
2252
+ await chmod(filePath, 384);
3020
2253
  }
3021
2254
  /**
3022
2255
  * Ranger le token obtenu par l'échange de jeton à usage unique.
@@ -3152,8 +2385,8 @@ var cloudSessionStore = new CloudSessionStore();
3152
2385
 
3153
2386
  // ../server/src/services/lan-mode.ts
3154
2387
  import { networkInterfaces } from "os";
3155
- import { readFile as readFile7, writeFile as writeFile4, mkdir as mkdir6 } from "fs/promises";
3156
- import { join as join6 } from "path";
2388
+ import { readFile as readFile6, writeFile as writeFile3, mkdir as mkdir5 } from "fs/promises";
2389
+ import { join as join5 } from "path";
3157
2390
  import { z as z4 } from "zod";
3158
2391
  var LOOPBACK2 = "127.0.0.1";
3159
2392
  var ANY = "0.0.0.0";
@@ -3166,11 +2399,11 @@ function instanceKey() {
3166
2399
  return env.DATA_DIR;
3167
2400
  }
3168
2401
  function networkFilePath() {
3169
- return join6(machineRoot(), NETWORK_FILE);
2402
+ return join5(machineRoot(), NETWORK_FILE);
3170
2403
  }
3171
2404
  async function readFileState() {
3172
2405
  try {
3173
- return NetworkFileSchema.parse(JSON.parse(await readFile7(networkFilePath(), "utf-8")));
2406
+ return NetworkFileSchema.parse(JSON.parse(await readFile6(networkFilePath(), "utf-8")));
3174
2407
  } catch {
3175
2408
  return { instances: {} };
3176
2409
  }
@@ -3181,12 +2414,12 @@ async function readPersisted() {
3181
2414
  }
3182
2415
  async function writePersisted(lanEnabled) {
3183
2416
  const root = machineRoot();
3184
- await mkdir6(root, { recursive: true });
2417
+ await mkdir5(root, { recursive: true });
3185
2418
  const state = await readFileState();
3186
2419
  state.instances[instanceKey()] = { lanEnabled };
3187
2420
  const filePath = networkFilePath();
3188
2421
  const tmpPath = filePath + ".tmp";
3189
- await writeFile4(tmpPath, JSON.stringify(state, null, 2), "utf-8");
2422
+ await writeFile3(tmpPath, JSON.stringify(state, null, 2), "utf-8");
3190
2423
  await renameWithRetry(tmpPath, filePath);
3191
2424
  }
3192
2425
  function registerHttpServer(server) {
@@ -4282,6 +3515,12 @@ function generateKanbanPopulatePrompt(board, scope) {
4282
3515
  lines.push("");
4283
3516
  return lines.join("\n");
4284
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
+ }
4285
3524
 
4286
3525
  // ../server/src/services/ai-runners/claude-code-runner.ts
4287
3526
  import { spawn } from "child_process";
@@ -4454,7 +3693,7 @@ function tailTranscript(opts) {
4454
3693
 
4455
3694
  // ../server/src/services/ai-runners/claude-pty-runner.ts
4456
3695
  async function getAgentManager() {
4457
- const { agentManager: agentManager2 } = await import("./agent-manager-R7BPQO6D.js");
3696
+ const { agentManager: agentManager2 } = await import("./agent-manager-46C4NIQ2.js");
4458
3697
  return agentManager2;
4459
3698
  }
4460
3699
  function buildPtyArgs(params, sessionId, opts) {
@@ -4472,7 +3711,7 @@ ${TOKEN_INSTRUCTIONS}`;
4472
3711
  "--append-system-prompt",
4473
3712
  systemPrompt,
4474
3713
  ...mcpArgs,
4475
- prompt
3714
+ ...opts.deferPrompt ? [] : [prompt]
4476
3715
  ];
4477
3716
  return args;
4478
3717
  }
@@ -4521,7 +3760,9 @@ async function runClaudeViaPty(params) {
4521
3760
  const { agentId, signal, skipTokenInstructions } = params;
4522
3761
  if (!agentId) throw new Error("runClaudeViaPty requires an agentId");
4523
3762
  const sessionId = params.sessionId && params.sessionId.length > 0 ? params.sessionId : randomUUID4();
4524
- const args = buildPtyArgs(params, sessionId, { excludeMcp: true });
3763
+ const deferPrompt = !!params.mcpConfig;
3764
+ const mcpReadyId = deferPrompt ? randomUUID4() : void 0;
3765
+ const args = buildPtyArgs(params, sessionId, { excludeMcp: true, deferPrompt });
4525
3766
  const agentMgr = await getAgentManager();
4526
3767
  const displayArgs = args.map((a) => {
4527
3768
  if (a === params.prompt) return "<prompt>";
@@ -4601,7 +3842,7 @@ async function runClaudeViaPty(params) {
4601
3842
  }
4602
3843
  }
4603
3844
  });
4604
- agentMgr.agentMutation(agentId, "ai.spawnPty", {
3845
+ const spawned = agentMgr.agentMutation(agentId, "ai.spawnPty", {
4605
3846
  cli: "claude",
4606
3847
  args,
4607
3848
  env: params.injectedEnv && Object.keys(params.injectedEnv).length > 0 ? params.injectedEnv : void 0,
@@ -4621,7 +3862,9 @@ async function runClaudeViaPty(params) {
4621
3862
  // signe elle-même, faute de requête HTTP d'où le tirer.
4622
3863
  mcpApiToken: params.apiToken || void 0,
4623
3864
  mcpProjectId: params.projectId || void 0,
4624
- 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
4625
3868
  }).then(({ spawnId: id }) => {
4626
3869
  spawnId = id;
4627
3870
  if (signal.aborted) {
@@ -4631,6 +3874,18 @@ async function runClaudeViaPty(params) {
4631
3874
  }).catch((err) => {
4632
3875
  fail(new Error(`Failed to spawn claude PTY: ${err.message}`));
4633
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
+ }
4634
3889
  signal.addEventListener("abort", () => fail(new Error("Aborted")), { once: true });
4635
3890
  void tail.whenDone.then(() => {
4636
3891
  if (settled) return;
@@ -5139,7 +4394,7 @@ async function shutdownCodexAppServer() {
5139
4394
  }
5140
4395
  }
5141
4396
  async function getAgentManager2() {
5142
- const { agentManager: agentManager2 } = await import("./agent-manager-R7BPQO6D.js");
4397
+ const { agentManager: agentManager2 } = await import("./agent-manager-46C4NIQ2.js");
5143
4398
  return agentManager2;
5144
4399
  }
5145
4400
  function parseRuneyaCurlCommand(command) {
@@ -5867,12 +5122,6 @@ async function* drainSession(session, signal, isReconnect) {
5867
5122
  if (signal?.aborted) return;
5868
5123
  }
5869
5124
  }
5870
- async function kanbanContextFor(conv, scope) {
5871
- if (!conv?.kanbanBoardId) return "";
5872
- const kanbanBoard = await kanbanStore.get(conv.kanbanBoardId);
5873
- if (!kanbanBoard) return "";
5874
- return "\n\n" + (conv.kanbanCardId ? await buildKanbanCardPrompt(kanbanBoard, conv.kanbanCardId, scope) : generateKanbanPopulatePrompt(kanbanBoard, scope));
5875
- }
5876
5125
  async function cliToken(ctx) {
5877
5126
  const bearer = ctx.authorization?.startsWith("Bearer ") ? ctx.authorization.slice(7) : void 0;
5878
5127
  if (!bearer || ctx.callerType !== "app") return bearer;
@@ -6057,6 +5306,26 @@ async function conversationCwd(conv) {
6057
5306
  function startCliSession(runner, convId, prompt, opts = {}) {
6058
5307
  return runner === "codex" ? startCodexSession(convId, prompt, opts) : startClaudeCodeSession(convId, prompt, opts);
6059
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
+ }
6060
5329
  var chatRouter = router({
6061
5330
  conversations: router({
6062
5331
  /**
@@ -6839,7 +6108,12 @@ ${input.prompt}` : input.prompt;
6839
6108
  attachedContexts: z6.array(z6.object({
6840
6109
  label: z6.string().max(200),
6841
6110
  text: z6.string().max(1e6)
6842
- })).max(20).optional()
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()
6843
6117
  })).mutation(async ({ input, ctx }) => {
6844
6118
  const t0 = Date.now();
6845
6119
  const lap = (label) => {
@@ -6911,6 +6185,8 @@ ${input.prompt}` : input.prompt;
6911
6185
  }
6912
6186
  const injectedEnv = await resolveInjectedEnv(input.environmentId);
6913
6187
  lap("resolveInjectedEnv");
6188
+ const hasStartupInput = (input.attachedContexts?.length ?? 0) > 0 || !!input.initialPrompt?.trim();
6189
+ const mcpReadyId = input.enableMcp && hasStartupInput ? randomUUID5() : void 0;
6914
6190
  const res = await agentManager.agentMutation(
6915
6191
  agentId,
6916
6192
  "ai.spawnPty",
@@ -6936,65 +6212,23 @@ ${input.prompt}` : input.prompt;
6936
6212
  mcpApiBaseUrl: input.apiBaseUrl ?? `http://localhost:${env.PORT}`,
6937
6213
  mcpApiToken: jwt,
6938
6214
  ...ptyRef?.id ? { mcpProjectId: ptyRef.id } : {},
6939
- ...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 } : {}
6940
6218
  }
6941
6219
  );
6942
6220
  lap("agent ai.spawnPty");
6943
- const contexts = [...input.attachedContexts ?? []];
6944
- if (contexts.length > 0) {
6945
- const body = contexts.map((c) => `### ${c.label}
6946
- ${c.text}`).join("\n\n");
6947
- const injection = `\x1B[200~---------
6948
- Context:
6949
- \x1B[201~\x1B[200~${body}\x1B[201~\x1B[200~
6950
- ---------
6951
- \x1B[201~`;
6952
- const doInject = () => {
6953
- agentManager.agentMutation(agentId, "ai.writePty", { spawnId: res.spawnId, data: injection }).catch((err) => {
6954
- console.warn("[chat.pty.start] deferred context writePty failed:", err.message);
6955
- });
6956
- };
6957
- const startedAt = Date.now();
6958
- const MIN_MS = input.runner === "codex" ? 500 : 1200;
6959
- const QUIET_MS = 400;
6960
- const MAX_MS = 8e3;
6961
- let injected = false;
6962
- let quietTimer = null;
6963
- let maxTimer = null;
6964
- let unsub = null;
6965
- const cleanup = () => {
6966
- if (quietTimer) clearTimeout(quietTimer);
6967
- if (maxTimer) clearTimeout(maxTimer);
6968
- unsub?.();
6969
- };
6970
- const inject = () => {
6971
- if (injected) return;
6972
- injected = true;
6973
- cleanup();
6974
- doInject();
6975
- };
6976
- const scheduleQuiet = () => {
6977
- if (quietTimer) clearTimeout(quietTimer);
6978
- const wait = Math.max(QUIET_MS, MIN_MS - (Date.now() - startedAt));
6979
- quietTimer = setTimeout(inject, wait);
6980
- };
6981
- maxTimer = setTimeout(inject, MAX_MS);
6982
- 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({
6983
6225
  agentId,
6984
- "ai.stream",
6985
- { spawnId: res.spawnId, fromOffset: 0 },
6986
- (data) => {
6987
- const chunk = data.chunk;
6988
- if (chunk?.type === "exit") {
6989
- injected = true;
6990
- cleanup();
6991
- return;
6992
- }
6993
- scheduleQuiet();
6994
- },
6995
- () => {
6996
- }
6997
- );
6226
+ spawnId: res.spawnId,
6227
+ runner: input.runner,
6228
+ mcpReadyId,
6229
+ contexts,
6230
+ initialPrompt
6231
+ });
6998
6232
  }
6999
6233
  noteSpawnConversation(res.spawnId, input.sessionId);
7000
6234
  return { spawnId: res.spawnId, agentId, reattached: false };
@@ -7255,9 +6489,6 @@ export {
7255
6489
  cloudFetch,
7256
6490
  readCloudIdentity,
7257
6491
  attachServiceToProject,
7258
- environmentOverridesStore,
7259
- filterEnvForAi,
7260
- environmentResolver,
7261
6492
  auditService,
7262
6493
  isShareable,
7263
6494
  logParserStore,
@@ -7270,6 +6501,7 @@ export {
7270
6501
  networkInfo,
7271
6502
  applyPersistedLanMode,
7272
6503
  setLanMode,
6504
+ runeyaMcpConfig,
7273
6505
  Id128Schema,
7274
6506
  ConversationIdSchema,
7275
6507
  NameSchema,
@@ -7293,6 +6525,7 @@ export {
7293
6525
  executeKanbanCommands,
7294
6526
  executeKanbanBoardCommands,
7295
6527
  buildKanbanCardPrompt,
6528
+ kanbanContextFor,
7296
6529
  ClaudeCodeRunner,
7297
6530
  shutdownCodexAppServer,
7298
6531
  CodexRunner,
@@ -7302,4 +6535,4 @@ export {
7302
6535
  startCliSession,
7303
6536
  chatRouter
7304
6537
  };
7305
- //# sourceMappingURL=chunk-SEPAURTQ.js.map
6538
+ //# sourceMappingURL=chunk-2NE52CL5.js.map