@runeya/runeya 2.0.10 → 2.0.11

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 (226) hide show
  1. package/agent/index.js +354 -8
  2. package/agent/index.js.map +1 -1
  3. package/agent-manager-V47DLMYP-26VYXTSG.js +8 -0
  4. package/{chunk-DODGBBTE.js → chunk-6547GLUJ.js} +24 -7
  5. package/chunk-6547GLUJ.js.map +1 -0
  6. package/{dist-HZBWBT6S.js → dist-2H7XZQCV.js} +1233 -379
  7. package/dist-2H7XZQCV.js.map +1 -0
  8. package/index.js +5 -1
  9. package/index.js.map +1 -1
  10. package/package.json +1 -1
  11. package/web/assets/AddressBar.vue-BxQkuTJ-.js +1 -0
  12. package/web/assets/AgentScalarChatInterface.vue-BZyh4z8j.js +13 -0
  13. package/web/assets/AgentTerminalPage-SWnqcTm-.js +1 -0
  14. package/web/assets/AgentTerminalPage-anYDkj7H.css +1 -0
  15. package/web/assets/AiChatPage-PJvieNVr.js +1 -0
  16. package/web/assets/AppLayout-qD2ReaTx.js +1 -0
  17. package/web/assets/AuditPage-DCvW5-HA.js +1 -0
  18. package/web/assets/CodeInput.vue-DDs5Eel7.js +1 -0
  19. package/web/assets/Collection.vue-HH2Z976J.js +1 -0
  20. package/web/assets/CollectionAuthentication.vue-BbsKOTJ9.js +1 -0
  21. package/web/assets/CollectionCookies.vue-Dzlq0l8S.js +1 -0
  22. package/web/assets/CollectionEnvironment.vue-BS9B1OU-.js +1 -0
  23. package/web/assets/CollectionOverview.vue-DXXQHn9J.js +1 -0
  24. package/web/assets/CollectionScripts.vue-TVNOxHuu.js +1 -0
  25. package/web/assets/CollectionServers.vue-DDyH4n80.js +1 -0
  26. package/web/assets/CollectionSettings.vue-BD5QvG5c.js +1 -0
  27. package/web/assets/CollectionSync.vue-g_NhkdXC.js +1 -0
  28. package/web/assets/CommandActionInput.vue-Cot3fJDd.js +1 -0
  29. package/web/assets/Cookies.vue-X8bc_7ud.js +1 -0
  30. package/web/assets/DashboardPage-BuP2oOo3.js +1 -0
  31. package/web/assets/DataTable.vue-DmcNIRcD.js +1 -0
  32. package/web/assets/DataTableCheckbox.vue-B7bvOQ_f.js +1 -0
  33. package/web/assets/{DataTableHeader.vue-U2le7KBO.js → DataTableHeader.vue-D5k0UCfl.js} +1 -1
  34. package/web/assets/DataTableInput.vue-DVUCY60y.js +1 -0
  35. package/web/assets/DeleteSidebarListElement.vue-CAri6AWj.js +1 -0
  36. package/web/assets/EditSidebarListElement.vue-DFY1ZZNE.js +1 -0
  37. package/web/assets/{EditorIcon-BPEaZrXC.js → EditorIcon-DOV6wP8X.js} +2 -2
  38. package/web/assets/{EmptyState.vue-Be8_w2rR.js → EmptyState.vue-watFzTsD.js} +2 -2
  39. package/web/assets/Environment.vue-BbpisYLs.js +1 -0
  40. package/web/assets/EnvironmentForm-CyJtNH2k.js +108 -0
  41. package/web/assets/EnvironmentFormPage-Du4EXZlm.js +1 -0
  42. package/web/assets/EnvironmentListPage-1Jdh0AWO.js +1 -0
  43. package/web/assets/EnvironmentModal.vue-DaI310h8.js +1 -0
  44. package/web/assets/EnvironmentSelector-BPhBcKYj.js +1 -0
  45. package/web/assets/Form.vue-CMWUiD6f.js +1 -0
  46. package/web/assets/IconSelector.vue-90PWhHNq.js +1 -0
  47. package/web/assets/{KanbanBoardPage-7rLK-ucb.js → KanbanBoardPage-DzFRgXlJ.js} +6 -6
  48. package/web/assets/KanbanListPage-O3T-L6z3.js +1 -0
  49. package/web/assets/LaunchProjectPage-B2m_PflY.js +1 -0
  50. package/web/assets/LaunchServicesPage-DgfE9971.js +1 -0
  51. package/web/assets/LogViewer-CXVXsymr.js +3 -0
  52. package/web/assets/ProcessControls-DVQwL-qx.js +1 -0
  53. package/web/assets/ProcessDetailPage-8GtSwfXG.js +1 -0
  54. package/web/assets/{ProcessMetrics-CLa8mdh2.js → ProcessMetrics-9RjmvDOk.js} +2 -2
  55. package/web/assets/ProjectDetailPage-B3s8bbmw.js +1 -0
  56. package/web/assets/ProjectListPage-HQ9gZWHw.js +1 -0
  57. package/web/assets/RandomJobTestPage-DAM5nNLf.js +2 -0
  58. package/web/assets/Request.vue-DiF4lAAD.js +1 -0
  59. package/web/assets/RequestAuth.vue-DHU9RNUf.js +1 -0
  60. package/web/assets/RequestRoot.vue-BiM2Tr20.js +19 -0
  61. package/web/assets/ScalarAsciiArt.vue-2vz_9aEa.js +2 -0
  62. package/web/assets/{ScalarIconEyeSlash.vue-qR90sHHe.js → ScalarIconEyeSlash.vue-CwJnZ-zD.js} +1 -1
  63. package/web/assets/{ScalarIconTrash.vue-Ll6Zw6_W.js → ScalarIconTrash.vue-DAEOI9Kk.js} +1 -1
  64. package/web/assets/ScenarioEditorPage-BzPL_7Up.js +14 -0
  65. package/web/assets/ServerVariablesForm.vue-Dj2R8IEV.js +1 -0
  66. package/web/assets/ServiceControlsBar-CsHZTuPK.js +1 -0
  67. package/web/assets/ServiceDetailPage-Csf55wQD.js +2 -0
  68. package/web/assets/ServiceFormPage-DDJYv2z3.js +1 -0
  69. package/web/assets/Settings.vue-Dyzw_xwE.js +1 -0
  70. package/web/assets/SettingsPage-CYQGpGyw.js +225 -0
  71. package/web/assets/SetupKeyPage-CnaFhUR6.js +1 -0
  72. package/web/assets/SetupPage-sqBZmTQ9.js +1 -0
  73. package/web/assets/Sidebar.vue-DqiliUT_.js +1 -0
  74. package/web/assets/SidebarButton.vue-MCBoC3jO.js +1 -0
  75. package/web/assets/SidebarListElement.vue-DswWWg_S.js +1 -0
  76. package/web/assets/UnlockPage-rDy89SdE.js +1 -0
  77. package/web/assets/VariablesTable-CEaksHyg.js +1 -0
  78. package/web/assets/{VariablesTable-BtdK5olT.css → VariablesTable-W4rxlL35.css} +1 -1
  79. package/web/assets/ViewLayout.vue-CgpeQh3K.js +1 -0
  80. package/web/assets/{ViewLayoutContent.vue-CAixUN8B.js → ViewLayoutContent.vue-DP_GK_Ip.js} +1 -1
  81. package/web/assets/ViewLayoutSection.vue-YQYzkcAB.js +1 -0
  82. package/web/assets/active-entities-CWFFm964.js +1 -0
  83. package/web/assets/api-reference-UJl6P2J4.js +1 -0
  84. package/web/assets/{autocomplete-Do-EC8XW.js → autocomplete-CK9Z1-Wz.js} +4 -4
  85. package/web/assets/card-DadsU5ZI.js +32 -0
  86. package/web/assets/{checkbox-C2hHZbMO.js → checkbox-4z5ftHhs.js} +3 -3
  87. package/web/assets/{chip-CD3EONN7.js → chip-KJbF4Jjk.js} +2 -2
  88. package/web/assets/column-Cp1kVQOt.js +763 -0
  89. package/web/assets/core-7y546zlP.js +1 -0
  90. package/web/assets/{cssMode-BzsFOrHs.js → cssMode-CIUATcSv.js} +1 -1
  91. package/web/assets/dist-BLSATgV0.js +1 -0
  92. package/web/assets/{dist-CiBpb7F_.js → dist-Cp1hgSqY.js} +2 -2
  93. package/web/assets/{dist-BmCpMB7W.js → dist-CpE_ZYIQ.js} +1 -1
  94. package/web/assets/dist-DR4LqG1l.js +192 -0
  95. package/web/assets/{divider-C7F2jaMI.js → divider-D-vBLTer.js} +2 -2
  96. package/web/assets/editor.store-COpS72tz.js +1 -0
  97. package/web/assets/{fieldset-kJDAoVaz.js → fieldset-CSlJxjpZ.js} +2 -2
  98. package/web/assets/{freemarker2-qyf3DfQl.js → freemarker2-Dsxb69Av.js} +1 -1
  99. package/web/assets/{handlebars-Dt_j38Ok.js → handlebars-ChbvHuc-.js} +1 -1
  100. package/web/assets/{html-BM6JBSDe.js → html-DOIF2Zpc.js} +1 -1
  101. package/web/assets/{htmlMode-BWrj-eTf.js → htmlMode-DKICvQwb.js} +1 -1
  102. package/web/assets/index-A6eiSk8H.css +1 -0
  103. package/web/assets/{index-Dviyssba.js → index-C0UzL9Y-.js} +281 -246
  104. package/web/assets/{javascript-Nwj2LVeE.js → javascript-Bmt68E8X.js} +1 -1
  105. package/web/assets/{jsonMode-xQ2R28Fq.js → jsonMode-CYcvn9c0.js} +1 -1
  106. package/web/assets/{keyboard-CidI6qyD.js → keyboard-DZklF_AY.js} +2 -2
  107. package/web/assets/{lib-Bpt_o5V9.js → lib-B5TdN_ca.js} +1 -1
  108. package/web/assets/{library-CcEqwrWR.js → library-DtrGq8p8.js} +1 -1
  109. package/web/assets/{liquid-CNc1r-Ln.js → liquid-3ot_oRqd.js} +1 -1
  110. package/web/assets/{mdx-D5yTipSh.js → mdx-Cm1rlDNM.js} +1 -1
  111. package/web/assets/{mediaTypes-pLZCmXiT.js → mediaTypes-Bz4C7Sle.js} +2 -2
  112. package/web/assets/{message-B6M86BRU.js → message-DFReN7Fq.js} +2 -2
  113. package/web/assets/{minus-wesZvWq4.js → minus-t_EQEzUj.js} +2 -2
  114. package/web/assets/{multiselect-CHEmtEFg.js → multiselect-BZbgA1pl.js} +3 -3
  115. package/web/assets/{password-ISam7EYg.js → password-D82un95s.js} +4 -4
  116. package/web/assets/{plus-BBMmFC_c.js → plus-B-AhHpHU.js} +6 -6
  117. package/web/assets/pretty-ms-XBeIOdhN.js +202 -0
  118. package/web/assets/process.store-jv0SI_6q.js +1 -0
  119. package/web/assets/project.store-DHXLE6Lv.js +1 -0
  120. package/web/assets/{python-BOLsEvIQ.js → python-BuQCsdKw.js} +1 -1
  121. package/web/assets/{radiobutton-DwoOAEAF.js → radiobutton-Btattklk.js} +2 -2
  122. package/web/assets/{razor-JsBDv4HY.js → razor-DgnEPQLs.js} +1 -1
  123. package/web/assets/router-w5lyb-nn.js +1 -0
  124. package/web/assets/settings.store-bNnLJ43c.js +1 -0
  125. package/web/assets/store-DWOu5TTl.js +1840 -0
  126. package/web/assets/{tabpanel-DBInUEl5.js → tabpanel-CIyzJ44J.js} +3 -3
  127. package/web/assets/{tag-CQlI4fJK.js → tag-DV-szbCr.js} +2 -2
  128. package/web/assets/trpc-wp_QsEqG.js +1 -0
  129. package/web/assets/{tsMode-C7uad5vV.js → tsMode-ChD0IpIg.js} +1 -1
  130. package/web/assets/{typescript-C2IozRX9.js → typescript-BRYpIqKe.js} +1 -1
  131. package/web/assets/urls-CoBMqtDH.js +12 -0
  132. package/web/assets/{use-tree-walker-CJHEPXgB.js → use-tree-walker-M4-Gn01v.js} +1 -1
  133. package/web/assets/useAiChatDrawer-BhlTNoXz.js +1 -0
  134. package/web/assets/{useClipboard-DtO-z5Ls.js → useClipboard-Cro2wlg3.js} +1 -1
  135. package/web/assets/{useLayout-D-FCdkqa.js → useLayout-BciG4r-1.js} +1 -1
  136. package/web/assets/{usePluginManager-CPTBi3rk.js → usePluginManager-BonTfGSN.js} +1 -1
  137. package/web/assets/useSidebar-77_u4hef.js +1 -0
  138. package/web/assets/{vue-router-DSROI5EH.js → vue-router-DdBypSuY.js} +1 -1
  139. package/web/assets/{xml-DtJwg-5S.js → xml-Cz29Bxul.js} +1 -1
  140. package/web/assets/{yaml-sPGHMYDm.js → yaml-CiZl2tNW.js} +1 -1
  141. package/web/index.html +2 -2
  142. package/agent-manager-A4E5CXFT-6PJDFN7J.js +0 -8
  143. package/chunk-DODGBBTE.js.map +0 -1
  144. package/dist-HZBWBT6S.js.map +0 -1
  145. package/web/assets/AddressBar.vue-D_8vQPIF.js +0 -1
  146. package/web/assets/AgentScalarChatInterface.vue-Be-LbA03.js +0 -13
  147. package/web/assets/AgentTerminalPage-BZmmd1H1.js +0 -36
  148. package/web/assets/AgentTerminalPage-EX_n6P3x.css +0 -1
  149. package/web/assets/AiChatPage-kTk6yC6d.js +0 -1
  150. package/web/assets/AppLayout-jbNML_m4.js +0 -1
  151. package/web/assets/AuditPage-S4kDjrZ5.js +0 -1
  152. package/web/assets/CodeInput.vue-nIhSM0FX.js +0 -1
  153. package/web/assets/Collection.vue-CkwnxBu4.js +0 -1
  154. package/web/assets/CollectionAuthentication.vue-CiZinGpr.js +0 -1
  155. package/web/assets/CollectionCookies.vue-BLEJqAhX.js +0 -1
  156. package/web/assets/CollectionEnvironment.vue-3bWtuG1B.js +0 -1
  157. package/web/assets/CollectionOverview.vue-BheZFfD3.js +0 -1
  158. package/web/assets/CollectionScripts.vue-CsY6tWxk.js +0 -1
  159. package/web/assets/CollectionServers.vue-Ja8mYDaK.js +0 -1
  160. package/web/assets/CollectionSettings.vue-DOB9JUg8.js +0 -1
  161. package/web/assets/CollectionSync.vue-B-uKwdTU.js +0 -1
  162. package/web/assets/CommandActionInput.vue-BBijVqe7.js +0 -1
  163. package/web/assets/Cookies.vue-DgVzSolJ.js +0 -1
  164. package/web/assets/DashboardPage-BwZPpURp.js +0 -1
  165. package/web/assets/DataTable.vue-DXTWz2Vi.js +0 -1
  166. package/web/assets/DataTableCheckbox.vue-BA5yHmxd.js +0 -1
  167. package/web/assets/DataTableInput.vue-DpeNUUXc.js +0 -1
  168. package/web/assets/DeleteSidebarListElement.vue-C2SV2geU.js +0 -1
  169. package/web/assets/EditSidebarListElement.vue-DvvYjTMO.js +0 -1
  170. package/web/assets/Environment.vue-D0PdeVIp.js +0 -1
  171. package/web/assets/EnvironmentForm-Dm3dFrSU.js +0 -108
  172. package/web/assets/EnvironmentFormPage-ByVELlly.js +0 -1
  173. package/web/assets/EnvironmentListPage-DvaTK4NF.js +0 -1
  174. package/web/assets/EnvironmentModal.vue-Bssf0U5X.js +0 -1
  175. package/web/assets/EnvironmentSelector-4kpQpg5e.js +0 -1
  176. package/web/assets/Form.vue-Dv4a31zh.js +0 -1
  177. package/web/assets/IconSelector.vue-BsYvff6K.js +0 -1
  178. package/web/assets/KanbanListPage-Dn35VrDc.js +0 -1
  179. package/web/assets/LaunchProjectPage-Byemtd9C.js +0 -1
  180. package/web/assets/LaunchServicesPage-CHiV6T5Y.js +0 -1
  181. package/web/assets/LogViewer-KtrKiUZw.js +0 -3
  182. package/web/assets/ProcessControls-DfFxf6jZ.js +0 -1
  183. package/web/assets/ProcessDetailPage-BOI85Xo7.js +0 -1
  184. package/web/assets/ProjectDetailPage-CTEt0DSq.js +0 -1
  185. package/web/assets/ProjectListPage-BrTIxp9K.js +0 -1
  186. package/web/assets/RandomJobTestPage-D2OB5pqD.js +0 -2
  187. package/web/assets/Request.vue-DHQd9eyp.js +0 -1
  188. package/web/assets/RequestAuth.vue-CEVOgzEk.js +0 -1
  189. package/web/assets/RequestRoot.vue-BEtC7IEP.js +0 -19
  190. package/web/assets/ScalarAsciiArt.vue-CyYmv9BB.js +0 -2
  191. package/web/assets/ScenarioEditorPage-xCx9aW1X.js +0 -14
  192. package/web/assets/ServerVariablesForm.vue-D9sjD4AT.js +0 -1
  193. package/web/assets/ServiceControlsBar-DKYIgScL.js +0 -1
  194. package/web/assets/ServiceDetailPage-B5qBCc9A.js +0 -2
  195. package/web/assets/ServiceFormPage-BNpWmyj_.js +0 -1
  196. package/web/assets/Settings.vue-DWPIjz57.js +0 -1
  197. package/web/assets/SettingsPage-C5lWJT0Z.js +0 -225
  198. package/web/assets/SetupKeyPage-BiqO4PQr.js +0 -1
  199. package/web/assets/SetupPage-FEUuQSQ2.js +0 -1
  200. package/web/assets/Sidebar.vue-DVHNacfA.js +0 -1
  201. package/web/assets/SidebarButton.vue-C4QDgSym.js +0 -1
  202. package/web/assets/SidebarListElement.vue-Ch1aI_Bz.js +0 -1
  203. package/web/assets/UnlockPage-Dhg0hoP-.js +0 -1
  204. package/web/assets/VariablesTable-DFJHJ9DF.js +0 -1
  205. package/web/assets/ViewLayout.vue-svdAMKVP.js +0 -1
  206. package/web/assets/ViewLayoutSection.vue-C9VAyihF.js +0 -1
  207. package/web/assets/active-entities-Dpd4ny38.js +0 -1
  208. package/web/assets/api-reference-C9jD1TQ9.js +0 -1
  209. package/web/assets/card-CeTOcGyS.js +0 -32
  210. package/web/assets/column-xfOef4Rh.js +0 -763
  211. package/web/assets/core-Dq8cnTTd.js +0 -1
  212. package/web/assets/dist-CQoPOD_o.js +0 -192
  213. package/web/assets/dist-US_vG96F.js +0 -1
  214. package/web/assets/editor.store-Dzhoqquh.js +0 -1
  215. package/web/assets/index-DXutsd9M.css +0 -1
  216. package/web/assets/pretty-ms-Bj2IaA2r.js +0 -202
  217. package/web/assets/process.store-cvjJzD3H.js +0 -1
  218. package/web/assets/project.store-C94-7i6c.js +0 -1
  219. package/web/assets/router-BgHCboHx.js +0 -1
  220. package/web/assets/settings.store-BGScNTnu.js +0 -1
  221. package/web/assets/store-a88gBDkh.js +0 -1840
  222. package/web/assets/trpc-CgAubgRH.js +0 -1
  223. package/web/assets/urls-BuNgEu0U.js +0 -12
  224. package/web/assets/useAiChatDrawer-D6TRHaNu.js +0 -1
  225. package/web/assets/useSidebar-BoK12gD1.js +0 -1
  226. /package/{agent-manager-A4E5CXFT-6PJDFN7J.js.map → agent-manager-V47DLMYP-26VYXTSG.js.map} +0 -0
@@ -34,7 +34,7 @@ import {
34
34
  nativeParsers,
35
35
  serviceStore,
36
36
  traefikManager
37
- } from "./chunk-DODGBBTE.js";
37
+ } from "./chunk-6547GLUJ.js";
38
38
  import {
39
39
  generateKanbanPopulatePrompt,
40
40
  generateKanbanSystemPrompt,
@@ -121,27 +121,34 @@ import OpenAI2 from "openai";
121
121
  import { execFile as execFile2 } from "child_process";
122
122
  import { promisify } from "util";
123
123
  import { fileURLToPath } from "url";
124
- import { TRPCError as TRPCError10 } from "@trpc/server";
125
- import { z as z13 } from "zod";
124
+ import { readdir as readdir2, access, readFile as readFile8 } from "fs/promises";
125
+ import { watch } from "fs";
126
+ import { homedir as homedir3 } from "os";
127
+ import { join as join10 } from "path";
126
128
  import { randomUUID as randomUUID3 } from "crypto";
127
129
  import { readFile as readFile7, writeFile as writeFile7, rename as rename7, mkdir as mkdir8, chmod as chmod6 } from "fs/promises";
128
130
  import { join as join9 } from "path";
129
131
  import { EventEmitter } from "events";
132
+ import { TRPCError as TRPCError10 } from "@trpc/server";
133
+ import { z as z13 } from "zod";
130
134
  import pty from "node-pty";
131
135
  import { execSync } from "child_process";
132
136
  import pty2 from "node-pty";
133
- import { mkdir as mkdir10, rm, readFile as readFile9 } from "fs/promises";
134
- import { join as join10 } from "path";
137
+ import { mkdir as mkdir10, rm, readFile as readFile10 } from "fs/promises";
138
+ import { join as join11 } from "path";
135
139
  import { chmod as chmod7, mkdir as mkdir9, rename as rename8, writeFile as writeFile8 } from "fs/promises";
136
140
  import { dirname as dirname2, resolve as resolve2, sep } from "path";
137
- import { readFile as readFile8 } from "fs/promises";
141
+ import { readFile as readFile9 } from "fs/promises";
138
142
  import Anthropic from "@anthropic-ai/sdk";
139
143
  import OpenAI from "openai";
140
144
  import { randomUUID as randomUUID4 } from "crypto";
141
- import { mkdir as mkdir11, writeFile as writeFile9, readFile as readFile10, unlink as unlink2, readdir as readdir2 } from "fs/promises";
145
+ import { mkdir as mkdir11, writeFile as writeFile9, readFile as readFile11, unlink as unlink2, readdir as readdir3 } from "fs/promises";
142
146
  import { existsSync } from "fs";
143
- import { join as join11, extname, resolve as resolve3, sep as sep2 } from "path";
147
+ import { join as join12, extname, resolve as resolve3, sep as sep2 } from "path";
144
148
  import { spawn as spawn2 } from "child_process";
149
+ import { randomUUID as randomUUID5 } from "crypto";
150
+ import { open } from "fs/promises";
151
+ import { watch as watch2 } from "fs";
145
152
  import { spawn as spawn3 } from "child_process";
146
153
  import { createInterface } from "readline";
147
154
  import { EventEmitter as EventEmitter2 } from "events";
@@ -149,7 +156,7 @@ import { z as z15 } from "zod";
149
156
  import { TRPCError as TRPCError11 } from "@trpc/server";
150
157
  import Anthropic3 from "@anthropic-ai/sdk";
151
158
  import OpenAI3 from "openai";
152
- import { randomUUID as randomUUID6 } from "crypto";
159
+ import { randomUUID as randomUUID7 } from "crypto";
153
160
  import { TRPCError as TRPCError14 } from "@trpc/server";
154
161
  import { z as z16 } from "zod";
155
162
  import { TRPCError as TRPCError12 } from "@trpc/server";
@@ -157,46 +164,47 @@ import { TRPCError as TRPCError13 } from "@trpc/server";
157
164
  import Anthropic4 from "@anthropic-ai/sdk";
158
165
  import OpenAI4 from "openai";
159
166
  import { mkdir as mkdir12 } from "fs/promises";
160
- import { randomUUID as randomUUID5 } from "crypto";
161
- import { join as join12 } from "path";
162
- import { randomUUID as randomUUID7 } from "crypto";
167
+ import { randomUUID as randomUUID6 } from "crypto";
168
+ import { join as join13 } from "path";
169
+ import { randomUUID as randomUUID8 } from "crypto";
163
170
  import { TRPCError as TRPCError15 } from "@trpc/server";
164
171
  import { z as z17 } from "zod";
165
172
  import { z as z18 } from "zod";
166
173
  import { TRPCError as TRPCError16 } from "@trpc/server";
167
174
  import { z as z19 } from "zod";
168
- import { randomUUID as randomUUID8 } from "crypto";
175
+ import { randomUUID as randomUUID9 } from "crypto";
169
176
  import { EventEmitter as EventEmitter3 } from "events";
170
177
  import { z as z20 } from "zod";
171
178
  import { TRPCError as TRPCError17 } from "@trpc/server";
172
179
  import { z as z21 } from "zod";
173
180
  import { chmod as chmod8, stat as stat3 } from "fs/promises";
174
- import { join as join13 } from "path";
175
- import { writeFile as writeFile10 } from "fs/promises";
176
181
  import { join as join14 } from "path";
182
+ import { writeFile as writeFile10 } from "fs/promises";
183
+ import { join as join15 } from "path";
177
184
  import { createRequire } from "module";
178
185
  import { randomBytes as randomBytes3, createCipheriv, createDecipheriv } from "crypto";
179
- import { randomUUID as randomUUID9 } from "crypto";
186
+ import { randomUUID as randomUUID10 } from "crypto";
180
187
  import {
181
- readFile as readFile11,
188
+ readFile as readFile12,
182
189
  writeFile as writeFile11,
183
190
  rename as rename9,
184
191
  mkdir as mkdir13,
185
- access,
186
- readdir as readdir3
192
+ access as access2,
193
+ readdir as readdir4
187
194
  } from "fs/promises";
188
- import { join as join15 } from "path";
189
- import { randomBytes as randomBytes4, createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2 } from "crypto";
190
- import { readFile as readFile12, writeFile as writeFile12, chmod as chmod9, stat as stat4, rename as rename10 } from "fs/promises";
191
195
  import { join as join16 } from "path";
196
+ import { randomBytes as randomBytes4, createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2 } from "crypto";
197
+ import { readFile as readFile13, writeFile as writeFile12, chmod as chmod9, stat as stat4, rename as rename10 } from "fs/promises";
198
+ import { join as join17 } from "path";
192
199
  import { Entry } from "@napi-rs/keyring";
193
200
  import { hashRaw as argon2HashRaw } from "@node-rs/argon2";
194
- import { join as join17 } from "path";
201
+ import { join as join18 } from "path";
195
202
  import { stat as stat5 } from "fs/promises";
196
203
  import rateLimit from "express-rate-limit";
197
204
  import { existsSync as existsSync2, createReadStream } from "fs";
198
205
  import { mkdir as mkdir14 } from "fs/promises";
199
206
  import multer from "multer";
207
+ import { resolve as resolvePath } from "path";
200
208
  import dotenv from "dotenv";
201
209
  var jwtSecret = null;
202
210
  var jwtSecretGeneratedAt = 0;
@@ -573,7 +581,6 @@ var EnvironmentOverridesStore = class {
573
581
  const raw = await readFile3(this.getFilePath(), "utf-8");
574
582
  const parsed = JSON.parse(raw);
575
583
  if (!getEncryptionKey()) {
576
- this.loaded = true;
577
584
  return;
578
585
  }
579
586
  this.diskEncryptedSlots = extractEncryptedVariableSlots(
@@ -2982,200 +2989,6 @@ var apiKeysRouter = router({
2982
2989
  return { path: input.relativePath };
2983
2990
  })
2984
2991
  });
2985
- var Id128Schema = z13.string().max(128);
2986
- var ConversationIdSchema = z13.string().max(128);
2987
- var NameSchema = z13.string().min(1).max(200);
2988
- var ShortString200Schema = z13.string().max(200);
2989
- var DescriptionSchema = z13.string().max(2e3);
2990
- var OptionalProviderIdSchema = Id128Schema.optional();
2991
- function generateAdminLlmsTxt(services, jwt) {
2992
- const baseUrl = `http://localhost:${env.PORT}`;
2993
- const trpcBase = `${baseUrl}/api/trpc`;
2994
- const lines = [];
2995
- lines.push("# Runeya \u2014 Service Management API");
2996
- lines.push("");
2997
- lines.push("> You are operating as an authenticated admin with full access to all services.");
2998
- lines.push(`> Base URL: ${trpcBase}`);
2999
- if (jwt) {
3000
- lines.push("> Authentication: pass `Authorization: Bearer $RUNEYA_API_TOKEN` on all tRPC requests.");
3001
- }
3002
- lines.push("");
3003
- if (services.length === 0) {
3004
- lines.push("No services configured.");
3005
- return lines.join("\n");
3006
- }
3007
- lines.push("## Services");
3008
- lines.push("");
3009
- for (const svc of services) {
3010
- lines.push(`### ${svc.name}`);
3011
- lines.push(`- **ID**: \`${svc.id}\``);
3012
- if (svc.agentId) lines.push(`- **Agent ID**: \`${svc.agentId}\``);
3013
- if (svc.description) lines.push(`- **Description**: ${svc.description}`);
3014
- if (svc.cwd) lines.push(`- **Working directory**: \`${svc.cwd}\``);
3015
- lines.push("- **Permitted actions**: all");
3016
- lines.push("");
3017
- }
3018
- lines.push("## MCP Tools");
3019
- lines.push("");
3020
- lines.push("You have access to Runeya MCP tools to manage services. **Always prefer MCP tools** over raw HTTP calls.");
3021
- lines.push("Available tools: `list_services`, `get_service_status`, `start_service`, `stop_service`, `restart_service`, `get_logs`.");
3022
- lines.push("");
3023
- if (jwt) {
3024
- lines.push("**Important**: Pass the `authToken` parameter with value `$RUNEYA_API_TOKEN` (from environment) on every MCP tool call for authentication.");
3025
- lines.push("");
3026
- }
3027
- lines.push("Use the service IDs and agent IDs listed above when calling these tools.");
3028
- lines.push("");
3029
- lines.push("## API Reference (fallback)");
3030
- lines.push("");
3031
- lines.push("Only use raw HTTP calls if MCP tools are unavailable.");
3032
- lines.push("All endpoints use tRPC v11 over HTTP (`Content-Type: application/json`).");
3033
- lines.push("");
3034
- lines.push("**tRPC v11 request format:**");
3035
- lines.push('- **GET queries**: `?input={"id":"abc"}`');
3036
- lines.push('- **POST mutations**: JSON body `{"agentId":"<agentId>","processId":"<serviceId>"}`');
3037
- lines.push("");
3038
- lines.push(`### List services
3039
- \`GET ${trpcBase}/service.list\``);
3040
- lines.push("");
3041
- lines.push(`### Get service
3042
- \`GET ${trpcBase}/service.get?input={"agentId":"<agentId>","id":"<serviceId>"}\``);
3043
- lines.push("");
3044
- lines.push(`### Read logs
3045
- \`GET ${trpcBase}/process.logs?input={"agentId":"<agentId>","processId":"<serviceId>"}\``);
3046
- lines.push("");
3047
- lines.push(`### Start service
3048
- \`POST ${trpcBase}/process.start\`
3049
- Body: \`{"agentId":"<agentId>","processId":"<serviceId>"}\``);
3050
- lines.push("");
3051
- lines.push(`### Stop service
3052
- \`POST ${trpcBase}/process.stop\`
3053
- Body: \`{"agentId":"<agentId>","processId":"<serviceId>"}\``);
3054
- lines.push("");
3055
- lines.push(`### Restart service
3056
- \`POST ${trpcBase}/process.restart\`
3057
- Body: \`{"agentId":"<agentId>","processId":"<serviceId>"}\``);
3058
- lines.push("");
3059
- lines.push(`### Get service status
3060
- \`GET ${trpcBase}/process.status?input={"agentId":"<agentId>","processId":"<serviceId>"}\``);
3061
- lines.push("");
3062
- return lines.join("\n");
3063
- }
3064
- function generateLlmsTxt(apiKey, services) {
3065
- const { scopes } = apiKey;
3066
- const baseUrl = `http://localhost:${env.PORT}`;
3067
- const trpcBase = `${baseUrl}/api/trpc`;
3068
- const lines = [];
3069
- lines.push("# Runeya \u2014 Service Management API");
3070
- lines.push("");
3071
- lines.push("> This file describes the API surface available for this key.");
3072
- lines.push(`> Base URL: ${trpcBase}`);
3073
- lines.push(`> Authentication: pass this key as \`Authorization: Bearer ${apiKey.key}\` on all tRPC requests.`);
3074
- lines.push("");
3075
- if (services.length === 0) {
3076
- lines.push("No services accessible with this key.");
3077
- return lines.join("\n");
3078
- }
3079
- const permByServiceId = new Map(
3080
- scopes.services.map((p) => [p.serviceId, p])
3081
- );
3082
- lines.push("## Services");
3083
- lines.push("");
3084
- for (const svc of services) {
3085
- const perm = permByServiceId.get(svc.id);
3086
- const actions = perm?.actions ?? [];
3087
- lines.push(`### ${svc.name}`);
3088
- lines.push(`- **ID**: \`${svc.id}\``);
3089
- if (svc.agentId) lines.push(`- **Agent ID**: \`${svc.agentId}\``);
3090
- if (svc.description) lines.push(`- **Description**: ${svc.description}`);
3091
- if (svc.cwd) lines.push(`- **Working directory**: \`${svc.cwd}\``);
3092
- lines.push("- **Permitted actions**:");
3093
- if (actions.length === 0) {
3094
- lines.push(" - _(none)_");
3095
- } else {
3096
- for (const a of actions) lines.push(` - \`${a}\``);
3097
- }
3098
- const envPerms = perm?.environments ?? [];
3099
- if (envPerms.length > 0) {
3100
- lines.push("- **Environment access**:");
3101
- for (const ep of envPerms) {
3102
- const envActions = ep.actions.join(", ");
3103
- const secrets = ep.includeSecrets ? " _(includes secrets)_" : "";
3104
- lines.push(` - \`${ep.environmentId}\`: ${envActions}${secrets}`);
3105
- }
3106
- }
3107
- lines.push("");
3108
- }
3109
- lines.push("## API Reference");
3110
- lines.push("");
3111
- lines.push("All endpoints use tRPC v11 over HTTP (`Content-Type: application/json`).");
3112
- lines.push("");
3113
- lines.push("**Important \u2014 tRPC v11 request format:**");
3114
- lines.push('- **GET queries**: pass input as a plain JSON-encoded string in the `input` query param (no `{"json":...}` wrapper).');
3115
- lines.push(' Example: `?input={"id":"abc"}`');
3116
- lines.push('- **POST mutations**: send the input object as a plain JSON body (no `{"json":...}` wrapper).');
3117
- lines.push(' Example body: `{"agentId":"<agentId>","processId":"<serviceId>"}`');
3118
- lines.push("- The `agentId` is listed per service above.");
3119
- lines.push("- The `processId` is the same as the service `ID` listed above.");
3120
- lines.push("");
3121
- const allActions = new Set(scopes.services.flatMap((p) => p.actions));
3122
- const hasEnvRead = scopes.services.some((p) => p.environments.some((e) => e.actions.includes("env:read")));
3123
- const hasEnvWrite = scopes.services.some((p) => p.environments.some((e) => e.actions.includes("env:write")));
3124
- if (allActions.has("service:read")) {
3125
- lines.push("### List services");
3126
- lines.push(`\`GET ${trpcBase}/service.list\``);
3127
- lines.push("");
3128
- lines.push("### Get service");
3129
- lines.push(`\`GET ${trpcBase}/service.get?input={"agentId":"<agentId>","id":"<serviceId>"}\``);
3130
- lines.push("");
3131
- }
3132
- if (allActions.has("logs:read")) {
3133
- lines.push("### Read logs");
3134
- lines.push(`\`GET ${trpcBase}/process.logs?input={"agentId":"<agentId>","processId":"<serviceId>"}\``);
3135
- lines.push("");
3136
- }
3137
- if (allActions.has("service:start")) {
3138
- lines.push("### Start service");
3139
- lines.push(`\`POST ${trpcBase}/process.start\``);
3140
- lines.push('Body: `{"agentId":"<agentId>","processId":"<serviceId>"}`');
3141
- lines.push("");
3142
- }
3143
- if (allActions.has("service:stop")) {
3144
- lines.push("### Stop service");
3145
- lines.push(`\`POST ${trpcBase}/process.stop\``);
3146
- lines.push('Body: `{"agentId":"<agentId>","processId":"<serviceId>"}`');
3147
- lines.push("");
3148
- }
3149
- if (allActions.has("service:restart")) {
3150
- lines.push("### Restart service");
3151
- lines.push(`\`POST ${trpcBase}/process.restart\``);
3152
- lines.push('Body: `{"agentId":"<agentId>","processId":"<serviceId>"}`');
3153
- lines.push("");
3154
- }
3155
- if (allActions.has("service:status")) {
3156
- lines.push("### Get service status");
3157
- lines.push(`\`GET ${trpcBase}/process.status?input={"agentId":"<agentId>","processId":"<serviceId>"}\``);
3158
- lines.push("");
3159
- }
3160
- if (allActions.has("service:healthcheck")) {
3161
- lines.push("### Get service health");
3162
- lines.push(`\`GET ${trpcBase}/process.health?input={"agentId":"<agentId>","processId":"<serviceId>"}\``);
3163
- lines.push("");
3164
- }
3165
- if (hasEnvRead) {
3166
- lines.push("### Read environment variables (flat KEY=VALUE \u2014 secrets shown as *****)");
3167
- lines.push(`\`GET ${trpcBase}/environment.resolveFlat?input={"environmentId":"<environmentId>"}\``);
3168
- lines.push("Returns a flat `Record<string, string>`. Secret variables you are not authorized to see are shown as `*****`.");
3169
- lines.push("");
3170
- }
3171
- if (hasEnvWrite) {
3172
- lines.push("### Write environment variable");
3173
- lines.push(`\`POST ${trpcBase}/environment.upsertVariable\``);
3174
- lines.push('Body: `{"environmentId":"<environmentId>","key":"<key>","value":"<value>"}`');
3175
- lines.push("");
3176
- }
3177
- return lines.join("\n");
3178
- }
3179
2992
  var RUNEYA_CAPABILITIES = [
3180
2993
  {
3181
2994
  name: "list_services",
@@ -3410,43 +3223,8 @@ async function callTrpc(baseUrl, route, method, input, authToken) {
3410
3223
  const result = json["result"];
3411
3224
  return result?.["data"] ?? result ?? json;
3412
3225
  }
3413
- var CHAT_TOOLS = buildAnthropicTools(RUNEYA_CAPABILITIES);
3414
- var CHAT_TOOLS_OPENAI = buildOpenAITools(RUNEYA_CAPABILITIES);
3415
- var TOOL_SAFETY_INSTRUCTION = buildToolSafetyInstruction(RUNEYA_CAPABILITIES);
3416
- function hasServiceAction(apiKey, serviceId, action) {
3417
- if (!apiKey) return true;
3418
- const perm = apiKey.scopes.services.find((p) => p.serviceId === serviceId);
3419
- return perm?.actions.includes(action) ?? false;
3420
- }
3421
- var KNOWN_TOOL_NAMES = RUNEYA_CAPABILITIES.map((c) => c.name);
3422
- function normalizeToolName(raw) {
3423
- if (KNOWN_TOOL_NAMES.includes(raw)) return raw;
3424
- const match = KNOWN_TOOL_NAMES.find((t2) => raw.startsWith(t2));
3425
- return match ?? raw;
3426
- }
3427
- async function executeTool(rawName, input, apiKey, authToken) {
3428
- const name = normalizeToolName(rawName);
3429
- const cap = RUNEYA_CAPABILITIES.find((c) => c.name === name);
3430
- if (!cap) throw new Error(`Unknown tool: ${name}`);
3431
- const serviceId = input["serviceId"] ?? input["id"] ?? input["processId"];
3432
- if (serviceId) {
3433
- const actionMap = {
3434
- get_service_status: "service:status",
3435
- get_logs: "logs:read",
3436
- start_service: "service:start",
3437
- stop_service: "service:stop",
3438
- restart_service: "service:restart"
3439
- };
3440
- const action = actionMap[name];
3441
- if (action && !hasServiceAction(apiKey, serviceId, action)) {
3442
- throw new Error(`No '${action}' permission for service ${serviceId}`);
3443
- }
3444
- }
3445
- const params = Object.fromEntries(
3446
- Object.entries(input).map(([k, v]) => [k, v != null ? String(v) : void 0])
3447
- );
3448
- const baseUrl = `http://localhost:${env.PORT}`;
3449
- return callTrpc(baseUrl, cap.route, cap.method, cap.buildInput(params), authToken);
3226
+ function buildApiDocs(apiBaseUrl, apiToken) {
3227
+ return buildPromptDocs(apiBaseUrl, apiToken, RUNEYA_CAPABILITIES);
3450
3228
  }
3451
3229
  var FILENAME3 = "conversations.private.json";
3452
3230
  var ConversationStore = class {
@@ -3512,7 +3290,6 @@ var ConversationStore = class {
3512
3290
  async load() {
3513
3291
  if (this.loaded) return;
3514
3292
  if (!getEncryptionKey()) {
3515
- this.loaded = true;
3516
3293
  return;
3517
3294
  }
3518
3295
  try {
@@ -3584,7 +3361,7 @@ var ConversationStore = class {
3584
3361
  if (patch.workflowId !== void 0) conv.workflowId = patch.workflowId;
3585
3362
  if (patch.archived !== void 0) conv.archived = patch.archived;
3586
3363
  if (patch.codexThreadId !== void 0) conv.codexThreadId = patch.codexThreadId;
3587
- conv.updatedAt = Date.now();
3364
+ if (options?.touch !== false) conv.updatedAt = Date.now();
3588
3365
  await this.save();
3589
3366
  if (!options?.silent) {
3590
3367
  this.events.emit("changed", { type: "updated", conversation: this.snapshotConversation(conv) });
@@ -3610,26 +3387,442 @@ var ConversationStore = class {
3610
3387
  await this.load();
3611
3388
  return Array.from(this.conversations.values()).filter((c) => c.kanbanBoardId === boardId);
3612
3389
  }
3613
- async listByKanbanCardId(cardId) {
3614
- await this.load();
3615
- return Array.from(this.conversations.values()).filter((c) => c.kanbanCardId === cardId);
3390
+ async listByKanbanCardId(cardId) {
3391
+ await this.load();
3392
+ return Array.from(this.conversations.values()).filter((c) => c.kanbanCardId === cardId);
3393
+ }
3394
+ async delete(id) {
3395
+ await this.load();
3396
+ const existed = this.conversations.delete(id);
3397
+ await this.save();
3398
+ if (existed) {
3399
+ this.events.emit("changed", { type: "deleted", id });
3400
+ }
3401
+ }
3402
+ onChanged(handler) {
3403
+ this.events.on("changed", handler);
3404
+ }
3405
+ offChanged(handler) {
3406
+ this.events.off("changed", handler);
3407
+ }
3408
+ };
3409
+ var conversationStore = new ConversationStore();
3410
+ var PROJECTS_DIR = join10(homedir3(), ".claude", "projects");
3411
+ var DEFAULT_TITLES = /* @__PURE__ */ new Set(["", "New conversation", "Nouvelle conversation"]);
3412
+ async function findClaudeSessionFile(sessionId) {
3413
+ let entries;
3414
+ try {
3415
+ entries = await readdir2(PROJECTS_DIR);
3416
+ } catch {
3417
+ return null;
3418
+ }
3419
+ const filename = `${sessionId}.jsonl`;
3420
+ for (const dir of entries) {
3421
+ const candidate = join10(PROJECTS_DIR, dir, filename);
3422
+ try {
3423
+ await access(candidate);
3424
+ return candidate;
3425
+ } catch {
3426
+ }
3427
+ }
3428
+ return null;
3429
+ }
3430
+ async function claudeSessionExists(sessionId) {
3431
+ return await findClaudeSessionFile(sessionId) !== null;
3432
+ }
3433
+ async function readClaudeTranscript(sessionId) {
3434
+ const path = await findClaudeSessionFile(sessionId);
3435
+ if (!path) return { found: false, path: null, entries: [] };
3436
+ const raw = await readFile8(path, "utf-8");
3437
+ const entries = [];
3438
+ for (const line of raw.split("\n")) {
3439
+ if (!line.trim()) continue;
3440
+ try {
3441
+ entries.push(JSON.parse(line));
3442
+ } catch {
3443
+ }
3444
+ }
3445
+ return { found: true, path, entries };
3446
+ }
3447
+ function claudeEntriesToMessages(entries) {
3448
+ const messages = [];
3449
+ const toolPartById = /* @__PURE__ */ new Map();
3450
+ for (const entry of entries) {
3451
+ if (entry.type !== "user" && entry.type !== "assistant") continue;
3452
+ const msg = entry.message;
3453
+ if (!msg) continue;
3454
+ const role = msg.role === "user" || msg.role === "assistant" ? msg.role : entry.type;
3455
+ const content = msg.content;
3456
+ const texts = [];
3457
+ const parts = [];
3458
+ let consumedByToolResult = false;
3459
+ if (typeof content === "string") {
3460
+ texts.push(content);
3461
+ } else if (Array.isArray(content)) {
3462
+ for (const block of content) {
3463
+ if (typeof block === "string") {
3464
+ texts.push(block);
3465
+ continue;
3466
+ }
3467
+ if (!block || typeof block !== "object") continue;
3468
+ const b = block;
3469
+ if (b.type === "text" && typeof b.text === "string") {
3470
+ texts.push(b.text);
3471
+ parts.push({ type: "text", text: b.text });
3472
+ } else if (b.type === "tool_use" && b.id) {
3473
+ const part = {
3474
+ type: "tool",
3475
+ id: b.id,
3476
+ name: b.name ?? "tool",
3477
+ input: b.input,
3478
+ pending: true
3479
+ };
3480
+ parts.push(part);
3481
+ toolPartById.set(b.id, part);
3482
+ } else if (b.type === "tool_result" && b.tool_use_id) {
3483
+ const target = toolPartById.get(b.tool_use_id);
3484
+ if (target) {
3485
+ target.result = b.content;
3486
+ target.isError = b.is_error;
3487
+ target.pending = false;
3488
+ }
3489
+ consumedByToolResult = true;
3490
+ }
3491
+ }
3492
+ }
3493
+ if (role === "user" && consumedByToolResult && texts.length === 0) continue;
3494
+ const last = messages[messages.length - 1];
3495
+ if (last && last.role === role) {
3496
+ const joinedText = [last.content, texts.join("\n").trim()].filter(Boolean).join("\n");
3497
+ last.content = joinedText;
3498
+ if (parts.length > 0) {
3499
+ last.parts = [...last.parts ?? [], ...parts];
3500
+ }
3501
+ continue;
3502
+ }
3503
+ messages.push({
3504
+ role,
3505
+ content: texts.join("\n").trim(),
3506
+ parts: parts.length > 0 ? parts : void 0
3507
+ });
3508
+ }
3509
+ return messages;
3510
+ }
3511
+ async function syncConversationFromClaudeTranscript(conversationId) {
3512
+ const conv = await conversationStore.get(conversationId);
3513
+ if (!conv) return false;
3514
+ const transcript = await readClaudeTranscript(conversationId);
3515
+ if (!transcript.found) return false;
3516
+ const messages = claudeEntriesToMessages(transcript.entries);
3517
+ const isDefaultTitle = !conv.title || DEFAULT_TITLES.has(conv.title);
3518
+ const sameLength = messages.length === conv.messages.length;
3519
+ const sameLastContent = sameLength && (messages.at(-1)?.content ?? "") === (conv.messages.at(-1)?.content ?? "");
3520
+ if (sameLength && sameLastContent && !isDefaultTitle) {
3521
+ return false;
3522
+ }
3523
+ const patch = { messages };
3524
+ if (isDefaultTitle) {
3525
+ const firstUserMsg = messages.find((m) => m.role === "user" && m.content.trim().length > 0);
3526
+ if (firstUserMsg) {
3527
+ const firstLine = firstUserMsg.content.split("\n")[0].trim();
3528
+ patch.title = firstLine.length > 80 ? firstLine.slice(0, 80) + "\u2026" : firstLine;
3529
+ }
3530
+ }
3531
+ await conversationStore.update(conversationId, patch, { touch: false });
3532
+ return true;
3533
+ }
3534
+ var lastTranscriptActivity = /* @__PURE__ */ new Map();
3535
+ function getLastTranscriptActivity(sessionId) {
3536
+ return lastTranscriptActivity.get(sessionId) ?? null;
3537
+ }
3538
+ var started = false;
3539
+ var watchers = [];
3540
+ var pendingResyncs = /* @__PURE__ */ new Map();
3541
+ var RESYNC_DEBOUNCE_MS = 250;
3542
+ function scheduleResync(sessionId) {
3543
+ const existing = pendingResyncs.get(sessionId);
3544
+ if (existing) clearTimeout(existing);
3545
+ pendingResyncs.set(
3546
+ sessionId,
3547
+ setTimeout(() => {
3548
+ pendingResyncs.delete(sessionId);
3549
+ syncConversationFromClaudeTranscript(sessionId).catch((err) => {
3550
+ console.warn("[claude-transcript-sync] resync failed for", sessionId, err);
3551
+ });
3552
+ }, RESYNC_DEBOUNCE_MS)
3553
+ );
3554
+ }
3555
+ function handleFsEvent(filename) {
3556
+ if (!filename) return;
3557
+ if (!filename.endsWith(".jsonl")) return;
3558
+ const base = filename.split("/").pop();
3559
+ const sessionId = base.slice(0, -".jsonl".length);
3560
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(sessionId)) return;
3561
+ lastTranscriptActivity.set(sessionId, Date.now());
3562
+ scheduleResync(sessionId);
3563
+ }
3564
+ async function startClaudeTranscriptWatcher() {
3565
+ if (started) return;
3566
+ started = true;
3567
+ let topLevel;
3568
+ try {
3569
+ topLevel = await readdir2(PROJECTS_DIR);
3570
+ } catch {
3571
+ return;
3572
+ }
3573
+ try {
3574
+ const w = watch(PROJECTS_DIR, { recursive: true }, (_event, filename) => {
3575
+ handleFsEvent(filename ? filename.toString() : null);
3576
+ });
3577
+ w.on("error", (err) => console.warn("[claude-transcript-sync] watcher error:", err));
3578
+ watchers.push(w);
3579
+ return;
3580
+ } catch {
3581
+ }
3582
+ for (const dir of topLevel) {
3583
+ try {
3584
+ const w = watch(join10(PROJECTS_DIR, dir), (_event, filename) => {
3585
+ handleFsEvent(filename ? filename.toString() : null);
3586
+ });
3587
+ w.on("error", () => {
3588
+ });
3589
+ watchers.push(w);
3590
+ } catch {
3591
+ }
3592
+ }
3593
+ }
3594
+ var Id128Schema = z13.string().max(128);
3595
+ var ConversationIdSchema = z13.string().max(128);
3596
+ var NameSchema = z13.string().min(1).max(200);
3597
+ var ShortString200Schema = z13.string().max(200);
3598
+ var DescriptionSchema = z13.string().max(2e3);
3599
+ var OptionalProviderIdSchema = Id128Schema.optional();
3600
+ function generateAdminLlmsTxt(services, jwt) {
3601
+ const baseUrl = `http://localhost:${env.PORT}`;
3602
+ const trpcBase = `${baseUrl}/api/trpc`;
3603
+ const lines = [];
3604
+ lines.push("# Runeya \u2014 Service Management API");
3605
+ lines.push("");
3606
+ lines.push("> You are operating as an authenticated admin with full access to all services.");
3607
+ lines.push(`> Base URL: ${trpcBase}`);
3608
+ if (jwt) {
3609
+ lines.push("> Authentication: pass `Authorization: Bearer $RUNEYA_API_TOKEN` on all tRPC requests.");
3610
+ }
3611
+ lines.push("");
3612
+ if (services.length === 0) {
3613
+ lines.push("No services configured.");
3614
+ return lines.join("\n");
3615
+ }
3616
+ lines.push("## Services");
3617
+ lines.push("");
3618
+ for (const svc of services) {
3619
+ lines.push(`### ${svc.name}`);
3620
+ lines.push(`- **ID**: \`${svc.id}\``);
3621
+ if (svc.agentId) lines.push(`- **Agent ID**: \`${svc.agentId}\``);
3622
+ if (svc.description) lines.push(`- **Description**: ${svc.description}`);
3623
+ if (svc.cwd) lines.push(`- **Working directory**: \`${svc.cwd}\``);
3624
+ lines.push("- **Permitted actions**: all");
3625
+ lines.push("");
3626
+ }
3627
+ lines.push("## MCP Tools");
3628
+ lines.push("");
3629
+ lines.push("You have access to Runeya MCP tools to manage services. **Always prefer MCP tools** over raw HTTP calls.");
3630
+ lines.push("Available tools: `list_services`, `get_service_status`, `start_service`, `stop_service`, `restart_service`, `get_logs`.");
3631
+ lines.push("");
3632
+ if (jwt) {
3633
+ lines.push("**Important**: Pass the `authToken` parameter with value `$RUNEYA_API_TOKEN` (from environment) on every MCP tool call for authentication.");
3634
+ lines.push("");
3635
+ }
3636
+ lines.push("Use the service IDs and agent IDs listed above when calling these tools.");
3637
+ lines.push("");
3638
+ lines.push("## API Reference (fallback)");
3639
+ lines.push("");
3640
+ lines.push("Only use raw HTTP calls if MCP tools are unavailable.");
3641
+ lines.push("All endpoints use tRPC v11 over HTTP (`Content-Type: application/json`).");
3642
+ lines.push("");
3643
+ lines.push("**tRPC v11 request format:**");
3644
+ lines.push('- **GET queries**: `?input={"id":"abc"}`');
3645
+ lines.push('- **POST mutations**: JSON body `{"agentId":"<agentId>","processId":"<serviceId>"}`');
3646
+ lines.push("");
3647
+ lines.push(`### List services
3648
+ \`GET ${trpcBase}/service.list\``);
3649
+ lines.push("");
3650
+ lines.push(`### Get service
3651
+ \`GET ${trpcBase}/service.get?input={"agentId":"<agentId>","id":"<serviceId>"}\``);
3652
+ lines.push("");
3653
+ lines.push(`### Read logs
3654
+ \`GET ${trpcBase}/process.logs?input={"agentId":"<agentId>","processId":"<serviceId>"}\``);
3655
+ lines.push("");
3656
+ lines.push(`### Start service
3657
+ \`POST ${trpcBase}/process.start\`
3658
+ Body: \`{"agentId":"<agentId>","processId":"<serviceId>"}\``);
3659
+ lines.push("");
3660
+ lines.push(`### Stop service
3661
+ \`POST ${trpcBase}/process.stop\`
3662
+ Body: \`{"agentId":"<agentId>","processId":"<serviceId>"}\``);
3663
+ lines.push("");
3664
+ lines.push(`### Restart service
3665
+ \`POST ${trpcBase}/process.restart\`
3666
+ Body: \`{"agentId":"<agentId>","processId":"<serviceId>"}\``);
3667
+ lines.push("");
3668
+ lines.push(`### Get service status
3669
+ \`GET ${trpcBase}/process.status?input={"agentId":"<agentId>","processId":"<serviceId>"}\``);
3670
+ lines.push("");
3671
+ return lines.join("\n");
3672
+ }
3673
+ function generateLlmsTxt(apiKey, services) {
3674
+ const { scopes } = apiKey;
3675
+ const baseUrl = `http://localhost:${env.PORT}`;
3676
+ const trpcBase = `${baseUrl}/api/trpc`;
3677
+ const lines = [];
3678
+ lines.push("# Runeya \u2014 Service Management API");
3679
+ lines.push("");
3680
+ lines.push("> This file describes the API surface available for this key.");
3681
+ lines.push(`> Base URL: ${trpcBase}`);
3682
+ lines.push(`> Authentication: pass this key as \`Authorization: Bearer ${apiKey.key}\` on all tRPC requests.`);
3683
+ lines.push("");
3684
+ if (services.length === 0) {
3685
+ lines.push("No services accessible with this key.");
3686
+ return lines.join("\n");
3687
+ }
3688
+ const permByServiceId = new Map(
3689
+ scopes.services.map((p) => [p.serviceId, p])
3690
+ );
3691
+ lines.push("## Services");
3692
+ lines.push("");
3693
+ for (const svc of services) {
3694
+ const perm = permByServiceId.get(svc.id);
3695
+ const actions = perm?.actions ?? [];
3696
+ lines.push(`### ${svc.name}`);
3697
+ lines.push(`- **ID**: \`${svc.id}\``);
3698
+ if (svc.agentId) lines.push(`- **Agent ID**: \`${svc.agentId}\``);
3699
+ if (svc.description) lines.push(`- **Description**: ${svc.description}`);
3700
+ if (svc.cwd) lines.push(`- **Working directory**: \`${svc.cwd}\``);
3701
+ lines.push("- **Permitted actions**:");
3702
+ if (actions.length === 0) {
3703
+ lines.push(" - _(none)_");
3704
+ } else {
3705
+ for (const a of actions) lines.push(` - \`${a}\``);
3706
+ }
3707
+ const envPerms = perm?.environments ?? [];
3708
+ if (envPerms.length > 0) {
3709
+ lines.push("- **Environment access**:");
3710
+ for (const ep of envPerms) {
3711
+ const envActions = ep.actions.join(", ");
3712
+ const secrets = ep.includeSecrets ? " _(includes secrets)_" : "";
3713
+ lines.push(` - \`${ep.environmentId}\`: ${envActions}${secrets}`);
3714
+ }
3715
+ }
3716
+ lines.push("");
3717
+ }
3718
+ lines.push("## API Reference");
3719
+ lines.push("");
3720
+ lines.push("All endpoints use tRPC v11 over HTTP (`Content-Type: application/json`).");
3721
+ lines.push("");
3722
+ lines.push("**Important \u2014 tRPC v11 request format:**");
3723
+ lines.push('- **GET queries**: pass input as a plain JSON-encoded string in the `input` query param (no `{"json":...}` wrapper).');
3724
+ lines.push(' Example: `?input={"id":"abc"}`');
3725
+ lines.push('- **POST mutations**: send the input object as a plain JSON body (no `{"json":...}` wrapper).');
3726
+ lines.push(' Example body: `{"agentId":"<agentId>","processId":"<serviceId>"}`');
3727
+ lines.push("- The `agentId` is listed per service above.");
3728
+ lines.push("- The `processId` is the same as the service `ID` listed above.");
3729
+ lines.push("");
3730
+ const allActions = new Set(scopes.services.flatMap((p) => p.actions));
3731
+ const hasEnvRead = scopes.services.some((p) => p.environments.some((e) => e.actions.includes("env:read")));
3732
+ const hasEnvWrite = scopes.services.some((p) => p.environments.some((e) => e.actions.includes("env:write")));
3733
+ if (allActions.has("service:read")) {
3734
+ lines.push("### List services");
3735
+ lines.push(`\`GET ${trpcBase}/service.list\``);
3736
+ lines.push("");
3737
+ lines.push("### Get service");
3738
+ lines.push(`\`GET ${trpcBase}/service.get?input={"agentId":"<agentId>","id":"<serviceId>"}\``);
3739
+ lines.push("");
3740
+ }
3741
+ if (allActions.has("logs:read")) {
3742
+ lines.push("### Read logs");
3743
+ lines.push(`\`GET ${trpcBase}/process.logs?input={"agentId":"<agentId>","processId":"<serviceId>"}\``);
3744
+ lines.push("");
3745
+ }
3746
+ if (allActions.has("service:start")) {
3747
+ lines.push("### Start service");
3748
+ lines.push(`\`POST ${trpcBase}/process.start\``);
3749
+ lines.push('Body: `{"agentId":"<agentId>","processId":"<serviceId>"}`');
3750
+ lines.push("");
3751
+ }
3752
+ if (allActions.has("service:stop")) {
3753
+ lines.push("### Stop service");
3754
+ lines.push(`\`POST ${trpcBase}/process.stop\``);
3755
+ lines.push('Body: `{"agentId":"<agentId>","processId":"<serviceId>"}`');
3756
+ lines.push("");
3757
+ }
3758
+ if (allActions.has("service:restart")) {
3759
+ lines.push("### Restart service");
3760
+ lines.push(`\`POST ${trpcBase}/process.restart\``);
3761
+ lines.push('Body: `{"agentId":"<agentId>","processId":"<serviceId>"}`');
3762
+ lines.push("");
3763
+ }
3764
+ if (allActions.has("service:status")) {
3765
+ lines.push("### Get service status");
3766
+ lines.push(`\`GET ${trpcBase}/process.status?input={"agentId":"<agentId>","processId":"<serviceId>"}\``);
3767
+ lines.push("");
3768
+ }
3769
+ if (allActions.has("service:healthcheck")) {
3770
+ lines.push("### Get service health");
3771
+ lines.push(`\`GET ${trpcBase}/process.health?input={"agentId":"<agentId>","processId":"<serviceId>"}\``);
3772
+ lines.push("");
3773
+ }
3774
+ if (hasEnvRead) {
3775
+ lines.push("### Read environment variables (flat KEY=VALUE \u2014 secrets shown as *****)");
3776
+ lines.push(`\`GET ${trpcBase}/environment.resolveFlat?input={"environmentId":"<environmentId>"}\``);
3777
+ lines.push("Returns a flat `Record<string, string>`. Secret variables you are not authorized to see are shown as `*****`.");
3778
+ lines.push("");
3779
+ }
3780
+ if (hasEnvWrite) {
3781
+ lines.push("### Write environment variable");
3782
+ lines.push(`\`POST ${trpcBase}/environment.upsertVariable\``);
3783
+ lines.push('Body: `{"environmentId":"<environmentId>","key":"<key>","value":"<value>"}`');
3784
+ lines.push("");
3616
3785
  }
3617
- async delete(id) {
3618
- await this.load();
3619
- const existed = this.conversations.delete(id);
3620
- await this.save();
3621
- if (existed) {
3622
- this.events.emit("changed", { type: "deleted", id });
3786
+ return lines.join("\n");
3787
+ }
3788
+ var CHAT_TOOLS = buildAnthropicTools(RUNEYA_CAPABILITIES);
3789
+ var CHAT_TOOLS_OPENAI = buildOpenAITools(RUNEYA_CAPABILITIES);
3790
+ var TOOL_SAFETY_INSTRUCTION = buildToolSafetyInstruction(RUNEYA_CAPABILITIES);
3791
+ function hasServiceAction(apiKey, serviceId, action) {
3792
+ if (!apiKey) return true;
3793
+ const perm = apiKey.scopes.services.find((p) => p.serviceId === serviceId);
3794
+ return perm?.actions.includes(action) ?? false;
3795
+ }
3796
+ var KNOWN_TOOL_NAMES = RUNEYA_CAPABILITIES.map((c) => c.name);
3797
+ function normalizeToolName(raw) {
3798
+ if (KNOWN_TOOL_NAMES.includes(raw)) return raw;
3799
+ const match = KNOWN_TOOL_NAMES.find((t2) => raw.startsWith(t2));
3800
+ return match ?? raw;
3801
+ }
3802
+ async function executeTool(rawName, input, apiKey, authToken) {
3803
+ const name = normalizeToolName(rawName);
3804
+ const cap = RUNEYA_CAPABILITIES.find((c) => c.name === name);
3805
+ if (!cap) throw new Error(`Unknown tool: ${name}`);
3806
+ const serviceId = input["serviceId"] ?? input["id"] ?? input["processId"];
3807
+ if (serviceId) {
3808
+ const actionMap = {
3809
+ get_service_status: "service:status",
3810
+ get_logs: "logs:read",
3811
+ start_service: "service:start",
3812
+ stop_service: "service:stop",
3813
+ restart_service: "service:restart"
3814
+ };
3815
+ const action = actionMap[name];
3816
+ if (action && !hasServiceAction(apiKey, serviceId, action)) {
3817
+ throw new Error(`No '${action}' permission for service ${serviceId}`);
3623
3818
  }
3624
3819
  }
3625
- onChanged(handler) {
3626
- this.events.on("changed", handler);
3627
- }
3628
- offChanged(handler) {
3629
- this.events.off("changed", handler);
3630
- }
3631
- };
3632
- var conversationStore = new ConversationStore();
3820
+ const params = Object.fromEntries(
3821
+ Object.entries(input).map(([k, v]) => [k, v != null ? String(v) : void 0])
3822
+ );
3823
+ const baseUrl = `http://localhost:${env.PORT}`;
3824
+ return callTrpc(baseUrl, cap.route, cap.method, cap.buildInput(params), authToken);
3825
+ }
3633
3826
  var MONTHS = {
3634
3827
  jan: 0,
3635
3828
  feb: 1,
@@ -4341,7 +4534,7 @@ async function atomicWriteJsonSecure(filePath, data, pretty = false) {
4341
4534
  }
4342
4535
  async function readJsonFileSafe(filePath) {
4343
4536
  try {
4344
- const raw = await readFile8(filePath, "utf-8");
4537
+ const raw = await readFile9(filePath, "utf-8");
4345
4538
  return JSON.parse(raw);
4346
4539
  } catch {
4347
4540
  return null;
@@ -4387,8 +4580,8 @@ var ScenarioRunStore = class _ScenarioRunStore {
4387
4580
  "- Use read-only references from linked upstream files as context.",
4388
4581
  ""
4389
4582
  ].join("\n");
4390
- await atomicWriteSecure(join10(dir, "README.md"), overview);
4391
- await atomicWriteSecure(join10(dir, "USER_REQUEST.md"), `# User request
4583
+ await atomicWriteSecure(join11(dir, "README.md"), overview);
4584
+ await atomicWriteSecure(join11(dir, "USER_REQUEST.md"), `# User request
4392
4585
 
4393
4586
  ${userInput.trim() || "(empty)"}
4394
4587
  `);
@@ -4414,7 +4607,7 @@ ${userInput.trim() || "(empty)"}
4414
4607
  "- Waiting for assignment.",
4415
4608
  ""
4416
4609
  ].join("\n");
4417
- await atomicWriteSecure(join10(dir, fileName), content);
4610
+ await atomicWriteSecure(join11(dir, fileName), content);
4418
4611
  }
4419
4612
  }
4420
4613
  async deleteRunDir(conversationId) {
@@ -4424,11 +4617,32 @@ ${userInput.trim() || "(empty)"}
4424
4617
  ensureRunState(conversationId) {
4425
4618
  let runState = this.activeRuns.get(conversationId);
4426
4619
  if (!runState) {
4427
- runState = { nodeStates: {}, nodeOutputs: {}, waitingInputs: {} };
4620
+ runState = { nodeStates: {}, nodeOutputs: {}, nodeSessionIds: {}, nodeAgentIds: {}, waitingInputs: {} };
4428
4621
  this.activeRuns.set(conversationId, runState);
4429
4622
  }
4430
4623
  return runState;
4431
4624
  }
4625
+ setNodeSession(conversationId, nodeId, sessionId, agentId) {
4626
+ const rs = this.ensureRunState(conversationId);
4627
+ rs.nodeSessionIds[nodeId] = sessionId;
4628
+ rs.nodeAgentIds[nodeId] = agentId;
4629
+ }
4630
+ getNodeSession(conversationId, nodeId) {
4631
+ const rs = this.activeRuns.get(conversationId);
4632
+ const sessionId = rs?.nodeSessionIds[nodeId];
4633
+ const agentId = rs?.nodeAgentIds[nodeId];
4634
+ if (sessionId && agentId) return { sessionId, agentId };
4635
+ return null;
4636
+ }
4637
+ async getNodeSessionPersistent(conversationId, nodeId) {
4638
+ const live = this.getNodeSession(conversationId, nodeId);
4639
+ if (live) return live;
4640
+ const persisted = await this.loadPersistedRunState(conversationId);
4641
+ const sessionId = persisted?.nodeSessionIds?.[nodeId];
4642
+ const agentId = persisted?.nodeAgentIds?.[nodeId];
4643
+ if (sessionId && agentId) return { sessionId, agentId };
4644
+ return null;
4645
+ }
4432
4646
  setNodeState(conversationId, nodeId, state) {
4433
4647
  this.ensureRunState(conversationId).nodeStates[nodeId] = state;
4434
4648
  }
@@ -4457,20 +4671,22 @@ ${userInput.trim() || "(empty)"}
4457
4671
  const data = {
4458
4672
  nodeStates: { ...runState.nodeStates },
4459
4673
  nodeOutputs: { ...runState.nodeOutputs },
4674
+ ...Object.keys(runState.nodeSessionIds).length > 0 ? { nodeSessionIds: { ...runState.nodeSessionIds } } : {},
4675
+ ...Object.keys(runState.nodeAgentIds).length > 0 ? { nodeAgentIds: { ...runState.nodeAgentIds } } : {},
4460
4676
  ...Object.keys(runState.waitingInputs).length > 0 ? { waitingInputs: { ...runState.waitingInputs } } : {}
4461
4677
  };
4462
- await atomicWriteJsonSecure(join10(dir, "run-state.json"), data);
4678
+ await atomicWriteJsonSecure(join11(dir, "run-state.json"), data);
4463
4679
  } catch {
4464
4680
  }
4465
4681
  }
4466
4682
  async loadPersistedRunState(conversationId) {
4467
4683
  const dir = this.getRunDir(conversationId);
4468
- return readJsonFileSafe(join10(dir, "run-state.json"));
4684
+ return readJsonFileSafe(join11(dir, "run-state.json"));
4469
4685
  }
4470
4686
  async readUserRequest(conversationId) {
4471
4687
  try {
4472
4688
  const dir = this.getRunDir(conversationId);
4473
- const content = await readFile9(join10(dir, "USER_REQUEST.md"), "utf-8");
4689
+ const content = await readFile10(join11(dir, "USER_REQUEST.md"), "utf-8");
4474
4690
  return content.replace(/^#\s*User request\s*\n+/, "").trim() || null;
4475
4691
  } catch {
4476
4692
  return null;
@@ -4479,7 +4695,7 @@ ${userInput.trim() || "(empty)"}
4479
4695
  async readReadme(conversationId) {
4480
4696
  try {
4481
4697
  const dir = this.getRunDir(conversationId);
4482
- return await readFile9(join10(dir, "README.md"), "utf-8");
4698
+ return await readFile10(join11(dir, "README.md"), "utf-8");
4483
4699
  } catch {
4484
4700
  return null;
4485
4701
  }
@@ -4488,14 +4704,14 @@ ${userInput.trim() || "(empty)"}
4488
4704
  const dir = this.getRunDir(conversationId);
4489
4705
  await mkdir10(dir, { recursive: true });
4490
4706
  await atomicWriteJsonSecure(
4491
- join10(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE),
4707
+ join11(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE),
4492
4708
  scenario,
4493
4709
  true
4494
4710
  );
4495
4711
  }
4496
4712
  async loadScenarioSnapshot(conversationId) {
4497
4713
  const dir = this.getRunDir(conversationId);
4498
- return readJsonFileSafe(join10(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE));
4714
+ return readJsonFileSafe(join11(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE));
4499
4715
  }
4500
4716
  };
4501
4717
  var scenarioRunStore = new ScenarioRunStore();
@@ -4570,7 +4786,7 @@ var ImageStorageService = class {
4570
4786
  const imageId = randomUUID4();
4571
4787
  const safeExt = ext.replace(/[^a-z.]/g, "");
4572
4788
  const fileName = `${imageId}${safeExt}`;
4573
- const filePath = join11(this.dir, fileName);
4789
+ const filePath = join12(this.dir, fileName);
4574
4790
  const resolvedPath = resolve3(filePath);
4575
4791
  const resolvedDir = resolve3(this.dir);
4576
4792
  if (resolvedPath !== resolvedDir && !resolvedPath.startsWith(resolvedDir + sep2)) {
@@ -4584,10 +4800,10 @@ var ImageStorageService = class {
4584
4800
  async getImagePath(imageId) {
4585
4801
  if (!UUID_RE.test(imageId)) return null;
4586
4802
  try {
4587
- const files = await readdir2(this.dir);
4803
+ const files = await readdir3(this.dir);
4588
4804
  const metaFile = files.find((f) => f.startsWith(imageId) && f.endsWith(".meta.json"));
4589
4805
  if (!metaFile) return null;
4590
- const raw = JSON.parse(await readFile10(join11(this.dir, metaFile), "utf-8"));
4806
+ const raw = JSON.parse(await readFile11(join12(this.dir, metaFile), "utf-8"));
4591
4807
  if (!existsSync(raw.filePath)) return null;
4592
4808
  return { filePath: raw.filePath, mimeType: raw.mimeType };
4593
4809
  } catch {
@@ -4605,14 +4821,14 @@ var ImageStorageService = class {
4605
4821
  async deleteByContextId(contextId) {
4606
4822
  let files;
4607
4823
  try {
4608
- files = await readdir2(this.dir);
4824
+ files = await readdir3(this.dir);
4609
4825
  } catch {
4610
4826
  return;
4611
4827
  }
4612
4828
  await Promise.all(
4613
4829
  files.filter((f) => f.endsWith(".meta.json")).map(async (file) => {
4614
4830
  try {
4615
- const raw = JSON.parse(await readFile10(join11(this.dir, file), "utf-8"));
4831
+ const raw = JSON.parse(await readFile11(join12(this.dir, file), "utf-8"));
4616
4832
  if (raw.contextId === contextId) {
4617
4833
  await unlink2(raw.filePath).catch(() => {
4618
4834
  });
@@ -4627,14 +4843,14 @@ var ImageStorageService = class {
4627
4843
  async readImageAsBase64(imageId) {
4628
4844
  const result = await this.getImagePath(imageId);
4629
4845
  if (!result) return null;
4630
- const buffer = await readFile10(result.filePath);
4846
+ const buffer = await readFile11(result.filePath);
4631
4847
  return { base64: buffer.toString("base64"), mimeType: result.mimeType };
4632
4848
  }
4633
4849
  async listExpiredImages(ttlMs = IMAGE_TTL_MS) {
4634
4850
  const expired = [];
4635
4851
  let files;
4636
4852
  try {
4637
- files = await readdir2(this.dir);
4853
+ files = await readdir3(this.dir);
4638
4854
  } catch {
4639
4855
  return [];
4640
4856
  }
@@ -4642,7 +4858,7 @@ var ImageStorageService = class {
4642
4858
  for (const file of files) {
4643
4859
  if (!file.endsWith(".meta.json")) continue;
4644
4860
  try {
4645
- const raw = JSON.parse(await readFile10(join11(this.dir, file), "utf-8"));
4861
+ const raw = JSON.parse(await readFile11(join12(this.dir, file), "utf-8"));
4646
4862
  if (now - raw.createdAt > ttlMs) expired.push(raw);
4647
4863
  } catch {
4648
4864
  }
@@ -4702,9 +4918,6 @@ async function resolveImagesToBase64(images) {
4702
4918
  );
4703
4919
  return results.filter((r) => r !== null);
4704
4920
  }
4705
- function buildApiDocs(apiBaseUrl, apiToken) {
4706
- return buildPromptDocs(apiBaseUrl, apiToken, RUNEYA_CAPABILITIES);
4707
- }
4708
4921
  function parseEndToken(output) {
4709
4922
  const tail = output.trimEnd().slice(-2e3);
4710
4923
  const branchMatch = tail.match(/<pass\s+branch="(\d+)"\s*\/>/);
@@ -4736,8 +4949,297 @@ var TOKEN_INSTRUCTIONS = `
4736
4949
 
4737
4950
  Ne jamais omettre ce tag. Il doit \xEAtre la toute derni\xE8re ligne de ta r\xE9ponse.
4738
4951
  `.trim();
4952
+ async function waitForFile(sessionId, signal, timeoutMs) {
4953
+ const start = Date.now();
4954
+ while (Date.now() - start < timeoutMs) {
4955
+ if (signal.aborted) throw new Error("Aborted");
4956
+ const path = await findClaudeSessionFile(sessionId);
4957
+ if (path) return path;
4958
+ await new Promise((r) => setTimeout(r, 150));
4959
+ }
4960
+ throw new Error(`Transcript file for session ${sessionId} never appeared (timeout ${timeoutMs}ms)`);
4961
+ }
4962
+ function tailTranscript(opts) {
4963
+ const { sessionId, signal, onEntry, fileAppearTimeoutMs = 3e4 } = opts;
4964
+ let stopped = false;
4965
+ let watcher = null;
4966
+ let fh = null;
4967
+ let offset = 0;
4968
+ let pendingBuf = "";
4969
+ let reading = false;
4970
+ let queuedRead = false;
4971
+ const cleanup = async () => {
4972
+ stopped = true;
4973
+ if (watcher) {
4974
+ try {
4975
+ watcher.close();
4976
+ } catch {
4977
+ }
4978
+ watcher = null;
4979
+ }
4980
+ if (fh) {
4981
+ try {
4982
+ await fh.close();
4983
+ } catch {
4984
+ }
4985
+ fh = null;
4986
+ }
4987
+ };
4988
+ const drainBuffer = () => {
4989
+ let idx;
4990
+ while ((idx = pendingBuf.indexOf("\n")) >= 0) {
4991
+ const line = pendingBuf.slice(0, idx).trim();
4992
+ pendingBuf = pendingBuf.slice(idx + 1);
4993
+ if (!line) continue;
4994
+ try {
4995
+ const entry = JSON.parse(line);
4996
+ onEntry(entry);
4997
+ } catch {
4998
+ }
4999
+ }
5000
+ };
5001
+ const readMore = async () => {
5002
+ if (!fh || stopped) return;
5003
+ if (reading) {
5004
+ queuedRead = true;
5005
+ return;
5006
+ }
5007
+ reading = true;
5008
+ try {
5009
+ while (!stopped) {
5010
+ const st = await fh.stat();
5011
+ if (st.size <= offset) break;
5012
+ const len = st.size - offset;
5013
+ const buf = Buffer.alloc(len);
5014
+ const { bytesRead } = await fh.read(buf, 0, len, offset);
5015
+ if (bytesRead <= 0) break;
5016
+ offset += bytesRead;
5017
+ pendingBuf += buf.slice(0, bytesRead).toString("utf-8");
5018
+ drainBuffer();
5019
+ }
5020
+ } finally {
5021
+ reading = false;
5022
+ if (queuedRead && !stopped) {
5023
+ queuedRead = false;
5024
+ void readMore();
5025
+ }
5026
+ }
5027
+ };
5028
+ const whenDone = (async () => {
5029
+ const onAbort = () => {
5030
+ void cleanup();
5031
+ };
5032
+ signal.addEventListener("abort", onAbort, { once: true });
5033
+ try {
5034
+ const path = await waitForFile(sessionId, signal, fileAppearTimeoutMs);
5035
+ if (stopped) return;
5036
+ fh = await open(path, "r");
5037
+ await readMore();
5038
+ if (stopped) return;
5039
+ watcher = watch2(path, { persistent: false }, () => {
5040
+ void readMore();
5041
+ });
5042
+ watcher.on("error", () => {
5043
+ });
5044
+ const poll = setInterval(() => {
5045
+ void readMore();
5046
+ }, 500);
5047
+ await new Promise((resolve5) => {
5048
+ const tick = setInterval(() => {
5049
+ if (stopped) {
5050
+ clearInterval(tick);
5051
+ clearInterval(poll);
5052
+ resolve5();
5053
+ }
5054
+ }, 100);
5055
+ });
5056
+ } finally {
5057
+ signal.removeEventListener("abort", onAbort);
5058
+ await cleanup();
5059
+ }
5060
+ })();
5061
+ return {
5062
+ stop: () => {
5063
+ void cleanup();
5064
+ },
5065
+ whenDone
5066
+ };
5067
+ }
4739
5068
  async function getAgentManager() {
4740
- const { agentManager: agentManager2 } = await import("./agent-manager-A4E5CXFT-6PJDFN7J.js");
5069
+ const { agentManager: agentManager2 } = await import("./agent-manager-V47DLMYP-26VYXTSG.js");
5070
+ return agentManager2;
5071
+ }
5072
+ function buildPtyArgs(params, sessionId, opts) {
5073
+ const { isResume, systemContext, apiBaseUrl, apiToken, model, mcpConfig, skipTokenInstructions, prompt } = params;
5074
+ const sessionArg = isResume ? ["--resume", sessionId] : ["--session-id", sessionId];
5075
+ const apiDocs = buildApiDocs(apiBaseUrl, apiToken);
5076
+ const fullSystemContext = apiDocs ? `${systemContext}
5077
+
5078
+ ${apiDocs}` : systemContext;
5079
+ const systemPrompt = skipTokenInstructions ? fullSystemContext : `${fullSystemContext}
5080
+
5081
+ ${TOKEN_INSTRUCTIONS}`;
5082
+ const mcpArgs = mcpConfig && !opts.excludeMcp ? ["--mcp-config", mcpConfig] : [];
5083
+ const args = [
5084
+ ...sessionArg,
5085
+ "--dangerously-skip-permissions",
5086
+ "--model",
5087
+ model,
5088
+ "--append-system-prompt",
5089
+ systemPrompt,
5090
+ ...mcpArgs,
5091
+ prompt
5092
+ ];
5093
+ return args;
5094
+ }
5095
+ function dispatchEntry(entry, state, cbs) {
5096
+ if (entry.type !== "user" && entry.type !== "assistant") return;
5097
+ const msg = entry.message;
5098
+ if (!msg) return;
5099
+ const role = msg.role ?? entry.type;
5100
+ const content = msg.content;
5101
+ if (typeof content === "string") {
5102
+ if (role === "assistant" && content) {
5103
+ state.assistantText += content;
5104
+ cbs.onStatus("");
5105
+ cbs.onDelta(content);
5106
+ }
5107
+ return;
5108
+ }
5109
+ if (!Array.isArray(content)) return;
5110
+ for (const raw of content) {
5111
+ if (!raw || typeof raw !== "object") continue;
5112
+ const block = raw;
5113
+ if (block.type === "text" && typeof block.text === "string" && role === "assistant") {
5114
+ state.assistantText += block.text;
5115
+ cbs.onStatus("");
5116
+ cbs.onDelta(block.text);
5117
+ } else if (block.type === "thinking" && typeof block.thinking === "string") {
5118
+ cbs.onStatus("\u{1F4AD} En r\xE9flexion\u2026");
5119
+ cbs.onThinking?.(block.thinking);
5120
+ } else if (block.type === "tool_use" && block.id) {
5121
+ const name = block.name ?? "tool";
5122
+ state.tools.set(block.id, { id: block.id, name });
5123
+ cbs.onStatus(`\u{1F527} ${name}`);
5124
+ cbs.onToolCall?.(block.id, name, block.input ?? {});
5125
+ } else if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
5126
+ const c = block.content;
5127
+ let result = "";
5128
+ if (typeof c === "string") result = c;
5129
+ else if (Array.isArray(c)) {
5130
+ result = c.filter((b) => b["type"] === "text").map((b) => b["text"] ?? "").join("");
5131
+ }
5132
+ cbs.onToolResult?.(block.tool_use_id, result, block.is_error === true);
5133
+ }
5134
+ }
5135
+ }
5136
+ async function runClaudeViaPty(params) {
5137
+ const { agentId, signal, skipTokenInstructions } = params;
5138
+ if (!agentId) throw new Error("runClaudeViaPty requires an agentId");
5139
+ const sessionId = params.sessionId && params.sessionId.length > 0 ? params.sessionId : randomUUID5();
5140
+ const args = buildPtyArgs(params, sessionId, { excludeMcp: true });
5141
+ const agentMgr = await getAgentManager();
5142
+ const displayArgs = args.map((a) => {
5143
+ if (a === params.prompt) return "<prompt>";
5144
+ if (a.includes(TOKEN_INSTRUCTIONS)) return "<system-context>";
5145
+ return a;
5146
+ });
5147
+ params.onCommand?.(`claude ${displayArgs.join(" ")}`);
5148
+ return new Promise((resolve5, reject) => {
5149
+ let settled = false;
5150
+ let spawnId = null;
5151
+ const state = { assistantText: "", tools: /* @__PURE__ */ new Map() };
5152
+ const innerAbort = new AbortController();
5153
+ const onParentAbort = () => innerAbort.abort();
5154
+ signal.addEventListener("abort", onParentAbort, { once: true });
5155
+ const cleanup = () => {
5156
+ signal.removeEventListener("abort", onParentAbort);
5157
+ innerAbort.abort();
5158
+ };
5159
+ const killSpawn = async () => {
5160
+ if (!spawnId) return;
5161
+ try {
5162
+ await agentMgr.agentMutation(agentId, "ai.kill", { spawnId });
5163
+ } catch {
5164
+ }
5165
+ };
5166
+ const settle = (result) => {
5167
+ if (settled) return;
5168
+ settled = true;
5169
+ cleanup();
5170
+ void killSpawn();
5171
+ resolve5(result);
5172
+ };
5173
+ const fail = (err) => {
5174
+ if (settled) return;
5175
+ settled = true;
5176
+ cleanup();
5177
+ void killSpawn();
5178
+ reject(err);
5179
+ };
5180
+ const tail = tailTranscript({
5181
+ sessionId,
5182
+ signal: innerAbort.signal,
5183
+ onEntry: (entry) => {
5184
+ try {
5185
+ dispatchEntry(entry, state, {
5186
+ onDelta: params.onDelta,
5187
+ onStatus: params.onStatus,
5188
+ onThinking: params.onThinking,
5189
+ onToolCall: params.onToolCall,
5190
+ onToolResult: params.onToolResult
5191
+ });
5192
+ } catch {
5193
+ }
5194
+ if (!skipTokenInstructions) {
5195
+ const { status, message, branchChoice } = parseEndToken(state.assistantText);
5196
+ if (status !== "none") {
5197
+ settle({
5198
+ output: state.assistantText,
5199
+ tokenStatus: status,
5200
+ tokenMessage: message,
5201
+ branchChoice,
5202
+ actualSessionId: sessionId
5203
+ });
5204
+ }
5205
+ }
5206
+ }
5207
+ });
5208
+ agentMgr.agentMutation(agentId, "ai.spawnPty", {
5209
+ cli: "claude",
5210
+ args,
5211
+ cols: 120,
5212
+ rows: 30,
5213
+ sessionId,
5214
+ enableMcp: !!params.mcpConfig,
5215
+ mcpApiBaseUrl: params.apiBaseUrl || void 0
5216
+ }).then(({ spawnId: id }) => {
5217
+ spawnId = id;
5218
+ if (signal.aborted) {
5219
+ fail(new Error("Aborted"));
5220
+ return;
5221
+ }
5222
+ }).catch((err) => {
5223
+ fail(new Error(`Failed to spawn claude PTY: ${err.message}`));
5224
+ });
5225
+ signal.addEventListener("abort", () => fail(new Error("Aborted")), { once: true });
5226
+ void tail.whenDone.then(() => {
5227
+ if (settled) return;
5228
+ if (skipTokenInstructions) {
5229
+ settle({
5230
+ output: state.assistantText,
5231
+ tokenStatus: "none",
5232
+ tokenMessage: "",
5233
+ actualSessionId: sessionId
5234
+ });
5235
+ } else {
5236
+ fail(new Error("Claude PTY ended before producing an end token"));
5237
+ }
5238
+ });
5239
+ });
5240
+ }
5241
+ async function getAgentManager2() {
5242
+ const { agentManager: agentManager2 } = await import("./agent-manager-V47DLMYP-26VYXTSG.js");
4741
5243
  return agentManager2;
4742
5244
  }
4743
5245
  function createClaudeStreamState() {
@@ -4961,6 +5463,10 @@ function processClaudeStdoutLines(lines, streamState, params, ctx) {
4961
5463
  }
4962
5464
  var ClaudeCodeRunner = class {
4963
5465
  run(params) {
5466
+ const ptyEnabled = params.usePty ?? process.env["RUNEYA_CLAUDE_PTY"] === "1";
5467
+ if (ptyEnabled && params.agentId && !(params.images && params.images.length > 0)) {
5468
+ return runClaudeViaPty(params);
5469
+ }
4964
5470
  if (params.agentId) {
4965
5471
  return this.runViaAgent(params);
4966
5472
  }
@@ -4970,7 +5476,7 @@ var ClaudeCodeRunner = class {
4970
5476
  async runViaAgent(params) {
4971
5477
  const { agentId, signal, skipTokenInstructions } = params;
4972
5478
  const { args, env: env2, stdinData, sessionArg } = buildClaudeSpawnConfig(params, { excludeMcp: true });
4973
- const agentMgr = await getAgentManager();
5479
+ const agentMgr = await getAgentManager2();
4974
5480
  const totalArgSize = args.reduce((s, a) => s + a.length + 1, 0);
4975
5481
  console.log(`[claude-code-runner] [agent:${agentId}] arg size: ${totalArgSize} bytes, session: ${sessionArg.join(" ")}, images: ${params.images?.length ?? 0}`);
4976
5482
  const displayArgs = args.map((a) => {
@@ -5305,8 +5811,8 @@ async function shutdownCodexAppServer() {
5305
5811
  await client.shutdown();
5306
5812
  }
5307
5813
  }
5308
- async function getAgentManager2() {
5309
- const { agentManager: agentManager2 } = await import("./agent-manager-A4E5CXFT-6PJDFN7J.js");
5814
+ async function getAgentManager3() {
5815
+ const { agentManager: agentManager2 } = await import("./agent-manager-V47DLMYP-26VYXTSG.js");
5310
5816
  return agentManager2;
5311
5817
  }
5312
5818
  function parseRuneyaCurlCommand(command) {
@@ -5375,7 +5881,7 @@ var CodexRunner = class {
5375
5881
  // ── Run via agent (app-server JSON-RPC via agent tRPC routes) ───────────
5376
5882
  async runViaAgent(params) {
5377
5883
  const { agentId, prompt, systemContext, signal, model, effort, isResume, images, skipTokenInstructions, apiBaseUrl, apiToken } = params;
5378
- const agentMgr = await getAgentManager2();
5884
+ const agentMgr = await getAgentManager3();
5379
5885
  if (isResume && params.sessionId && !this.lastThreadId) {
5380
5886
  this.lastThreadId = params.sessionId;
5381
5887
  }
@@ -6638,6 +7144,325 @@ ${input.prompt}` : input.prompt;
6638
7144
  };
6639
7145
  runStream();
6640
7146
  yield* drainSession(session, signal, false);
7147
+ }),
7148
+ // ── PTY passthrough (interactive Claude Code session) ────────────────────
7149
+ pty: router({
7150
+ /** Start a PTY session running `claude --resume <sessionId>` (or fresh). */
7151
+ start: protectedProcedure.input(z14.object({
7152
+ agentId: z14.string().max(128).optional(),
7153
+ sessionId: z14.string().max(128).optional(),
7154
+ resume: z14.boolean().default(false),
7155
+ model: z14.string().max(128).optional(),
7156
+ cwd: z14.string().max(4096).optional(),
7157
+ cols: z14.number().int().min(1).max(1e3).default(120),
7158
+ rows: z14.number().int().min(1).max(1e3).default(30),
7159
+ enableMcp: z14.boolean().default(false),
7160
+ apiBaseUrl: z14.string().max(512).optional(),
7161
+ attachedContexts: z14.array(z14.object({
7162
+ label: z14.string().max(200),
7163
+ text: z14.string().max(1e6)
7164
+ })).max(20).optional()
7165
+ })).mutation(async ({ input, ctx }) => {
7166
+ const agentId = input.agentId ?? agentManager.getDefaultAgentId();
7167
+ if (!agentId) throw new TRPCError10({ code: "PRECONDITION_FAILED", message: "No agent available" });
7168
+ if (input.sessionId) {
7169
+ try {
7170
+ const existing = await agentManager.agentQuery(
7171
+ agentId,
7172
+ "ai.findPtyBySession",
7173
+ { sessionId: input.sessionId }
7174
+ );
7175
+ if (existing && existing.status === "running") {
7176
+ await agentManager.agentMutation(agentId, "ai.resizePty", {
7177
+ spawnId: existing.spawnId,
7178
+ cols: input.cols,
7179
+ rows: input.rows
7180
+ }).catch(() => {
7181
+ });
7182
+ return { spawnId: existing.spawnId, agentId, reattached: true };
7183
+ }
7184
+ } catch {
7185
+ }
7186
+ }
7187
+ const args = [];
7188
+ if (input.sessionId) {
7189
+ const sessionExists = await claudeSessionExists(input.sessionId);
7190
+ if (input.resume || sessionExists) {
7191
+ args.push("--resume", input.sessionId);
7192
+ } else {
7193
+ args.push("--session-id", input.sessionId);
7194
+ }
7195
+ }
7196
+ if (input.model) args.push("--model", input.model);
7197
+ args.push("--dangerously-skip-permissions");
7198
+ const jwt = ctx.authorization?.startsWith("Bearer ") ? ctx.authorization.slice(7) : void 0;
7199
+ if (input.sessionId) {
7200
+ try {
7201
+ const conv = await conversationStore.get(input.sessionId);
7202
+ if (conv) {
7203
+ const systemContext = await buildCliSystemContext(conv, jwt);
7204
+ const apiBase = input.apiBaseUrl ?? `http://localhost:${env.PORT}`;
7205
+ const apiDocs = buildApiDocs(apiBase, jwt ?? "");
7206
+ const fullSystemPrompt = apiDocs ? `${systemContext}
7207
+
7208
+ ${apiDocs}` : systemContext;
7209
+ if (fullSystemPrompt.trim().length > 0) {
7210
+ args.push("--append-system-prompt", fullSystemPrompt);
7211
+ }
7212
+ }
7213
+ } catch (err) {
7214
+ console.warn("[chat.pty.start] buildCliSystemContext failed:", err.message);
7215
+ }
7216
+ }
7217
+ const res = await agentManager.agentMutation(
7218
+ agentId,
7219
+ "ai.spawnPty",
7220
+ {
7221
+ cli: "claude",
7222
+ args,
7223
+ cwd: input.cwd,
7224
+ cols: input.cols,
7225
+ rows: input.rows,
7226
+ sessionId: input.sessionId,
7227
+ enableMcp: input.enableMcp,
7228
+ mcpApiBaseUrl: input.apiBaseUrl ?? `http://localhost:${env.PORT}`,
7229
+ mcpApiToken: jwt
7230
+ }
7231
+ );
7232
+ const contexts = input.attachedContexts ?? [];
7233
+ if (contexts.length > 0) {
7234
+ const body = contexts.map((c) => `### ${c.label}
7235
+ ${c.text}`).join("\n\n");
7236
+ const injection = `\x1B[200~---------
7237
+ Context:
7238
+ \x1B[201~\x1B[200~${body}\x1B[201~\x1B[200~
7239
+ ---------
7240
+ \x1B[201~`;
7241
+ setTimeout(() => {
7242
+ agentManager.agentMutation(agentId, "ai.writePty", { spawnId: res.spawnId, data: injection }).catch((err) => {
7243
+ console.warn("[chat.pty.start] deferred context writePty failed:", err.message);
7244
+ });
7245
+ }, 500);
7246
+ }
7247
+ return { spawnId: res.spawnId, agentId, reattached: false };
7248
+ }),
7249
+ /** Subscribe to PTY output stream (raw text with ANSI). */
7250
+ stream: protectedProcedure.input(z14.object({
7251
+ agentId: z14.string().max(128).optional(),
7252
+ spawnId: z14.string().max(128),
7253
+ fromOffset: z14.number().int().min(0).default(0)
7254
+ })).subscription(async function* ({ input, signal }) {
7255
+ const agentId = input.agentId ?? agentManager.getDefaultAgentId();
7256
+ if (!agentId) throw new TRPCError10({ code: "PRECONDITION_FAILED", message: "No agent available" });
7257
+ const queue = [];
7258
+ let resolve5 = null;
7259
+ let stopped = false;
7260
+ const unsub = agentManager.agentSubscribe(
7261
+ agentId,
7262
+ "ai.stream",
7263
+ { spawnId: input.spawnId, fromOffset: input.fromOffset },
7264
+ (data) => {
7265
+ queue.push(data);
7266
+ if (resolve5) {
7267
+ resolve5();
7268
+ resolve5 = null;
7269
+ }
7270
+ },
7271
+ () => {
7272
+ stopped = true;
7273
+ if (resolve5) {
7274
+ resolve5();
7275
+ resolve5 = null;
7276
+ }
7277
+ }
7278
+ );
7279
+ try {
7280
+ while (!signal?.aborted && !stopped) {
7281
+ while (queue.length > 0) {
7282
+ const item = queue.shift();
7283
+ yield item;
7284
+ if (item.chunk.type === "exit") {
7285
+ stopped = true;
7286
+ break;
7287
+ }
7288
+ }
7289
+ if (stopped) break;
7290
+ await new Promise((r) => {
7291
+ resolve5 = r;
7292
+ });
7293
+ }
7294
+ } finally {
7295
+ unsub();
7296
+ }
7297
+ }),
7298
+ /** Write input to a PTY session (keystrokes from xterm). */
7299
+ write: protectedProcedure.input(z14.object({
7300
+ agentId: z14.string().max(128).optional(),
7301
+ spawnId: z14.string().max(128),
7302
+ data: z14.string().max(1e6)
7303
+ })).mutation(async ({ input }) => {
7304
+ const agentId = input.agentId ?? agentManager.getDefaultAgentId();
7305
+ if (!agentId) throw new TRPCError10({ code: "PRECONDITION_FAILED", message: "No agent available" });
7306
+ return agentManager.agentMutation(agentId, "ai.writePty", {
7307
+ spawnId: input.spawnId,
7308
+ data: input.data
7309
+ });
7310
+ }),
7311
+ /** Resize a PTY session. */
7312
+ resize: protectedProcedure.input(z14.object({
7313
+ agentId: z14.string().max(128).optional(),
7314
+ spawnId: z14.string().max(128),
7315
+ cols: z14.number().int().min(1).max(1e3),
7316
+ rows: z14.number().int().min(1).max(1e3)
7317
+ })).mutation(async ({ input }) => {
7318
+ const agentId = input.agentId ?? agentManager.getDefaultAgentId();
7319
+ if (!agentId) throw new TRPCError10({ code: "PRECONDITION_FAILED", message: "No agent available" });
7320
+ return agentManager.agentMutation(agentId, "ai.resizePty", {
7321
+ spawnId: input.spawnId,
7322
+ cols: input.cols,
7323
+ rows: input.rows
7324
+ });
7325
+ }),
7326
+ /**
7327
+ * Upload a pasted image to the agent. The agent saves it to a tmp file and
7328
+ * tries to populate its system clipboard so Claude's native paste detection
7329
+ * picks it up. Returns the tmp path and which clipboard method succeeded
7330
+ * (null if none — caller should inject `@<path>` instead of Ctrl+V).
7331
+ */
7332
+ pasteImage: protectedProcedure.input(z14.object({
7333
+ agentId: z14.string().max(128).optional(),
7334
+ sessionId: z14.string().max(128).optional(),
7335
+ mimeType: z14.string().max(64),
7336
+ base64: z14.string().max(15e6)
7337
+ })).mutation(async ({ input }) => {
7338
+ const agentId = input.agentId ?? agentManager.getDefaultAgentId();
7339
+ if (!agentId) throw new TRPCError10({ code: "PRECONDITION_FAILED", message: "No agent available" });
7340
+ const res = await agentManager.agentMutation(agentId, "ai.pasteImage", {
7341
+ sessionId: input.sessionId,
7342
+ mimeType: input.mimeType,
7343
+ base64: input.base64
7344
+ });
7345
+ if (!res.clipboardMethod) {
7346
+ console.warn("[chat.pty.pasteImage] clipboard spoof failed, using @path fallback:", res.clipboardError);
7347
+ }
7348
+ return res;
7349
+ }),
7350
+ /**
7351
+ * Read the transcript Claude Code stores on disk for a given session.
7352
+ * Returns parsed JSONL entries (user / assistant messages, tool uses, etc.).
7353
+ */
7354
+ transcript: protectedProcedure.input(z14.object({ sessionId: z14.string().max(128) })).query(async ({ input }) => {
7355
+ return readClaudeTranscript(input.sessionId);
7356
+ }),
7357
+ /**
7358
+ * Lightweight liveness probe: is a PTY running for this session, and
7359
+ * when was the transcript JSONL last touched? Used by the non-PTY view
7360
+ * to show a "Claude is thinking…" indicator while output streams.
7361
+ */
7362
+ activity: protectedProcedure.input(z14.object({
7363
+ agentId: z14.string().max(128).optional(),
7364
+ sessionId: z14.string().max(128)
7365
+ })).query(async ({ input }) => {
7366
+ const agentId = input.agentId ?? agentManager.getDefaultAgentId();
7367
+ let running = false;
7368
+ let lastChunkAt = null;
7369
+ if (agentId) {
7370
+ try {
7371
+ const existing = await agentManager.agentQuery(
7372
+ agentId,
7373
+ "ai.findPtyBySession",
7374
+ { sessionId: input.sessionId }
7375
+ );
7376
+ running = existing?.status === "running";
7377
+ lastChunkAt = existing?.lastChunkAt ?? null;
7378
+ } catch {
7379
+ }
7380
+ }
7381
+ const transcriptActivityAt = getLastTranscriptActivity(input.sessionId);
7382
+ const lastActivityAt = lastChunkAt && transcriptActivityAt ? Math.max(lastChunkAt, transcriptActivityAt) : lastChunkAt ?? transcriptActivityAt;
7383
+ return { running, lastActivityAt };
7384
+ }),
7385
+ /**
7386
+ * Convert the on-disk Claude transcript into Runeya Message[] and persist
7387
+ * them on the conversation, so the non-PTY chat view stays in sync.
7388
+ * Uses the conversation id as session id (PTY sessions are spawned that way).
7389
+ */
7390
+ syncTranscript: protectedProcedure.input(z14.object({ conversationId: ConversationIdSchema })).mutation(async ({ input }) => {
7391
+ const conv = await conversationStore.get(input.conversationId);
7392
+ if (!conv) {
7393
+ throw new TRPCError10({ code: "NOT_FOUND", message: `Conversation ${input.conversationId} not found` });
7394
+ }
7395
+ const synced = await syncConversationFromClaudeTranscript(conv.id);
7396
+ const after = await conversationStore.get(conv.id);
7397
+ return { synced, messageCount: after?.messages.length ?? 0 };
7398
+ }),
7399
+ /**
7400
+ * Debug-only: spawn a PTY that emits random data at a chosen rate, tagged
7401
+ * with the given sessionId. Used to benchmark the PTY → tRPC → xterm.js
7402
+ * pipeline. Open the conversation in PTY mode to attach to it.
7403
+ */
7404
+ debugStress: protectedProcedure.input(z14.object({
7405
+ sessionId: z14.string().max(128),
7406
+ agentId: z14.string().max(128).optional(),
7407
+ bytesPerSec: z14.number().int().min(100).max(1e7).default(5e4),
7408
+ chunkSize: z14.number().int().min(1).max(65536).default(256),
7409
+ durationMs: z14.number().int().min(0).max(36e5).default(0),
7410
+ cols: z14.number().int().min(1).max(1e3).default(120),
7411
+ rows: z14.number().int().min(1).max(1e3).default(30)
7412
+ })).mutation(async ({ input }) => {
7413
+ const agentId = input.agentId ?? agentManager.getDefaultAgentId();
7414
+ if (!agentId) throw new TRPCError10({ code: "PRECONDITION_FAILED", message: "No agent available" });
7415
+ const stressSessionId = `${input.sessionId}-stress-${Date.now().toString(36)}`;
7416
+ const { existsSync: existsSync3 } = await import("fs");
7417
+ const candidates = [
7418
+ new URL("../../../../scripts/pty-stress-gen.mjs", import.meta.url),
7419
+ new URL("../../../../../scripts/pty-stress-gen.mjs", import.meta.url),
7420
+ new URL("../../../scripts/pty-stress-gen.mjs", import.meta.url)
7421
+ ];
7422
+ let scriptPath = null;
7423
+ for (const c of candidates) {
7424
+ const p = fileURLToPath(c);
7425
+ if (existsSync3(p)) {
7426
+ scriptPath = p;
7427
+ break;
7428
+ }
7429
+ }
7430
+ if (!scriptPath) {
7431
+ throw new TRPCError10({
7432
+ code: "INTERNAL_SERVER_ERROR",
7433
+ message: `pty-stress-gen.mjs not found near ${fileURLToPath(import.meta.url)}`
7434
+ });
7435
+ }
7436
+ const res = await agentManager.agentMutation(
7437
+ agentId,
7438
+ "ai.spawnPty",
7439
+ {
7440
+ cli: "node",
7441
+ args: [scriptPath],
7442
+ cwd: process.cwd(),
7443
+ cols: input.cols,
7444
+ rows: input.rows,
7445
+ sessionId: stressSessionId,
7446
+ env: {
7447
+ BYTES_PER_SEC: String(input.bytesPerSec),
7448
+ CHUNK_SIZE: String(input.chunkSize),
7449
+ DURATION_MS: String(input.durationMs)
7450
+ }
7451
+ }
7452
+ );
7453
+ return { spawnId: res.spawnId, agentId, stressSessionId };
7454
+ }),
7455
+ /** Kill a PTY session. */
7456
+ kill: protectedProcedure.input(z14.object({
7457
+ agentId: z14.string().max(128).optional(),
7458
+ spawnId: z14.string().max(128)
7459
+ })).mutation(async ({ input }) => {
7460
+ const agentId = input.agentId ?? agentManager.getDefaultAgentId();
7461
+ if (!agentId) throw new TRPCError10({ code: "PRECONDITION_FAILED", message: "No agent available" });
7462
+ return agentManager.agentMutation(agentId, "ai.kill", {
7463
+ spawnId: input.spawnId
7464
+ });
7465
+ })
6641
7466
  })
6642
7467
  });
6643
7468
  var SERVICE_ID_SCHEMA = z15.string().max(128);
@@ -7270,7 +8095,7 @@ function applyHireDirectivesFromOutput(manager, scenario, output) {
7270
8095
  }
7271
8096
  const hired = [];
7272
8097
  for (const payload of hireDirectives) {
7273
- const id = `hire-${randomUUID5().slice(0, 8)}`;
8098
+ const id = `hire-${randomUUID6().slice(0, 8)}`;
7274
8099
  const position = computeHirePosition(manager, scenario.nodes);
7275
8100
  ensureGroupCanContainNode(scenario, manager.groupId ?? null, position);
7276
8101
  const node = {
@@ -7634,6 +8459,9 @@ async function runNodeCall(conversationId, node, systemContext, userMessage, run
7634
8459
  model,
7635
8460
  effort: "normal",
7636
8461
  signal,
8462
+ // Scenarios default to interactive PTY to stay on subscription pricing.
8463
+ // Override with RUNEYA_CLAUDE_PTY=0 to fall back to headless `-p` mode.
8464
+ usePty: aiCli === "claude-code" && process.env["RUNEYA_CLAUDE_PTY"] !== "0",
7637
8465
  onDelta: (text) => {
7638
8466
  accumulated += text;
7639
8467
  onDelta(text);
@@ -7648,6 +8476,11 @@ async function runNodeCall(conversationId, node, systemContext, userMessage, run
7648
8476
  flushPersist();
7649
8477
  if (result.actualSessionId) state.sessionId = result.actualSessionId;
7650
8478
  state.isResume = true;
8479
+ const agentForNode = runOverrides?.agentId ?? agentManager.getDefaultAgentId() ?? null;
8480
+ if (state.sessionId && agentForNode) {
8481
+ scenarioRunStore.setNodeSession(conversationId, node.id, state.sessionId, agentForNode);
8482
+ void scenarioRunStore.persistRunState(conversationId);
8483
+ }
7651
8484
  if (result.tokenStatus === "ask_user") {
7652
8485
  publish2({ type: "node_duration", nodeId: node.id, durationMs: Date.now() - turnStart });
7653
8486
  scenarioRunStore.setNodeState(conversationId, node.id, "waiting_input");
@@ -7698,7 +8531,7 @@ async function appendNodeOutput(conversationId, node, output) {
7698
8531
  if (!dir) return;
7699
8532
  const slug = node.label.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 60) || "member";
7700
8533
  const safeId = node.id.replace(/[^a-z0-9_-]/gi, "-").slice(0, 80);
7701
- const filePath = join12(dir, `${slug}_${safeId}.md`);
8534
+ const filePath = join13(dir, `${slug}_${safeId}.md`);
7702
8535
  const content = `## Output
7703
8536
 
7704
8537
  ${output}
@@ -7713,7 +8546,7 @@ async function appendToReadme(conversationId, bus, text) {
7713
8546
  try {
7714
8547
  await mkdir12(dir, { recursive: true });
7715
8548
  const { appendFile: appendFile2 } = await import("fs/promises");
7716
- await appendFile2(join12(dir, "README.md"), line, "utf-8");
8549
+ await appendFile2(join13(dir, "README.md"), line, "utf-8");
7717
8550
  } catch {
7718
8551
  }
7719
8552
  publishToBus(bus, { type: "readme_append", text: line });
@@ -7733,7 +8566,7 @@ async function writeFinalMd(conversationId, bus, userRequest, content) {
7733
8566
 
7734
8567
  ${content}
7735
8568
  `;
7736
- const filePath = join12(dir, "FINAL.md");
8569
+ const filePath = join13(dir, "FINAL.md");
7737
8570
  await atomicWriteSecure(filePath, fullContent);
7738
8571
  await appendToReadme(
7739
8572
  conversationId,
@@ -8356,6 +9189,8 @@ ${out}`;
8356
9189
  ).catch(() => {
8357
9190
  });
8358
9191
  }
9192
+ await scenarioRunStore.persistRunState(conversationId).catch(() => {
9193
+ });
8359
9194
  publishToBus(bus, null);
8360
9195
  activeAbortControllers.delete(conversationId);
8361
9196
  }
@@ -8412,7 +9247,7 @@ var scenarioRouter = router({
8412
9247
  })).mutation(({ input }) => {
8413
9248
  const now = Date.now();
8414
9249
  return createOrBadRequest(() => scenarioStore.create({
8415
- id: randomUUID6(),
9250
+ id: randomUUID7(),
8416
9251
  name: input.name,
8417
9252
  icon: input.icon,
8418
9253
  description: input.description,
@@ -8533,6 +9368,17 @@ var scenarioRouter = router({
8533
9368
  }
8534
9369
  }
8535
9370
  }),
9371
+ /**
9372
+ * Returns the Claude Code session-id + agent-id for a node, so the UI can
9373
+ * open a debug PTY with `claude --resume <sessionId>` on the right agent.
9374
+ * Falls back to the persisted run-state for past nodes.
9375
+ */
9376
+ getNodeSession: protectedProcedure.input(z16.object({
9377
+ conversationId: ConversationIdSchema,
9378
+ nodeId: Id128Schema
9379
+ })).query(async ({ input }) => {
9380
+ return await scenarioRunStore.getNodeSessionPersistent(input.conversationId, input.nodeId);
9381
+ }),
8536
9382
  listPresets: protectedProcedure.query(() => listPresets()),
8537
9383
  getPreset: protectedProcedure.input(z16.object({ id: Id128Schema })).query(({ input }) => {
8538
9384
  const preset = getPreset(input.id);
@@ -8600,7 +9446,7 @@ var debugRouter = router({
8600
9446
  intervalMs: z17.number().int().min(100).max(5e3).optional(),
8601
9447
  maxTicks: z17.number().int().min(1).max(1e4).optional()
8602
9448
  }).optional()).mutation(({ input }) => {
8603
- const jobId = randomUUID7();
9449
+ const jobId = randomUUID8();
8604
9450
  const intervalMs = input?.intervalMs ?? 400;
8605
9451
  const maxTicks = input?.maxTicks ?? 500;
8606
9452
  const job = {
@@ -8786,7 +9632,7 @@ var KanbanQueueService = class extends EventEmitter3 {
8786
9632
  return { id: alreadyRunning.id, addedAt: alreadyRunning.startedAt, ...item };
8787
9633
  }
8788
9634
  const queueItem = {
8789
- id: randomUUID8(),
9635
+ id: randomUUID9(),
8790
9636
  ...item,
8791
9637
  addedAt: (/* @__PURE__ */ new Date()).toISOString()
8792
9638
  };
@@ -9174,7 +10020,7 @@ var SENSITIVE_FILES = [
9174
10020
  var SECURE_MODE = 384;
9175
10021
  async function ensureFilePermissions() {
9176
10022
  for (const filename of SENSITIVE_FILES) {
9177
- const filepath = join13(env.DATA_DIR, filename);
10023
+ const filepath = join14(env.DATA_DIR, filename);
9178
10024
  try {
9179
10025
  const stats = await stat3(filepath);
9180
10026
  const mode = stats.mode & 511;
@@ -9222,7 +10068,7 @@ conversations.private.json
9222
10068
  *.log.*
9223
10069
  `;
9224
10070
  async function ensureDataGitignore(dataDir) {
9225
- const gitignorePath = join14(dataDir, ".gitignore");
10071
+ const gitignorePath = join15(dataDir, ".gitignore");
9226
10072
  await writeFile10(gitignorePath, GITIGNORE_CONTENT, "utf-8");
9227
10073
  }
9228
10074
  var _require = createRequire(import.meta.url);
@@ -9331,7 +10177,7 @@ async function getSodium() {
9331
10177
  return _sodium;
9332
10178
  }
9333
10179
  async function decryptLegacyFile(filePath, key, sodium) {
9334
- const raw = (await readFile11(filePath, "utf-8")).trim();
10180
+ const raw = (await readFile12(filePath, "utf-8")).trim();
9335
10181
  if (!raw || raw === "[]" || raw === "{}") {
9336
10182
  try {
9337
10183
  return JSON.parse(raw);
@@ -9357,29 +10203,29 @@ async function atomicWrite(path, data) {
9357
10203
  async function runLegacyMigration(dataDir) {
9358
10204
  const { resolve: resolve5 } = await import("path");
9359
10205
  const abCwd = resolve5(process.cwd());
9360
- const legacyRoot = join15(abCwd, env.DATA_DIR);
9361
- const legacyKeyPath = join15(legacyRoot, "dbs", "encryption-key.json");
10206
+ const legacyRoot = join16(abCwd, env.DATA_DIR);
10207
+ const legacyKeyPath = join16(legacyRoot, "dbs", "encryption-key.json");
9362
10208
  try {
9363
- await access(legacyKeyPath);
10209
+ await access2(legacyKeyPath);
9364
10210
  } catch {
9365
10211
  return false;
9366
10212
  }
9367
10213
  console.log("[legacy-migration] Legacy Runeya data detected, starting migration\u2026");
9368
10214
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
9369
- const legacyTmp = join15(abCwd, ".runeya-tmp");
10215
+ const legacyTmp = join16(abCwd, ".runeya-tmp");
9370
10216
  await rename9(legacyRoot, legacyTmp);
9371
10217
  await mkdir13(dataDir, { recursive: true });
9372
- const legacyOld = join15(dataDir, `backup-${timestamp}`);
10218
+ const legacyOld = join16(dataDir, `backup-${timestamp}`);
9373
10219
  await rename9(legacyTmp, legacyOld);
9374
10220
  console.log(`[legacy-migration] Legacy data backed up to ${legacyOld}`);
9375
- const keyJson = JSON.parse(await readFile11(join15(legacyOld, "dbs", "encryption-key.json"), "utf-8"));
10221
+ const keyJson = JSON.parse(await readFile12(join16(legacyOld, "dbs", "encryption-key.json"), "utf-8"));
9376
10222
  const sodium = await getSodium();
9377
10223
  const sodiumKey = sodium.from_base64(keyJson.encryptionKey);
9378
10224
  const keyBuffer = Buffer.from(sodiumKey);
9379
10225
  const runyeaKeyHex = keyBuffer.toString("hex");
9380
10226
  await setEncryptionKey(runyeaKeyHex);
9381
10227
  console.log("[legacy-migration] Encryption key loaded in memory (temporary)");
9382
- const dbsDir = join15(legacyOld, "dbs");
10228
+ const dbsDir = join16(legacyOld, "dbs");
9383
10229
  async function readEncrypted(filePath) {
9384
10230
  try {
9385
10231
  return await decryptLegacyFile(filePath, sodiumKey, sodium);
@@ -9388,29 +10234,29 @@ async function runLegacyMigration(dataDir) {
9388
10234
  return null;
9389
10235
  }
9390
10236
  }
9391
- const envsDir = join15(dbsDir, "envs");
9392
- const envFiles = (await readdir3(envsDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
10237
+ const envsDir = join16(dbsDir, "envs");
10238
+ const envFiles = (await readdir4(envsDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
9393
10239
  const legacyEnvs = {};
9394
10240
  for (const file of envFiles) {
9395
10241
  const name = file.replace(".encrypted.json", "");
9396
- legacyEnvs[name] = await readEncrypted(join15(envsDir, file));
10242
+ legacyEnvs[name] = await readEncrypted(join16(envsDir, file));
9397
10243
  }
9398
- const servicesDir = join15(dbsDir, "services");
9399
- const serviceFiles = (await readdir3(servicesDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
10244
+ const servicesDir = join16(dbsDir, "services");
10245
+ const serviceFiles = (await readdir4(servicesDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
9400
10246
  const legacyServices = {};
9401
10247
  for (const file of serviceFiles) {
9402
10248
  const name = file.replace(".encrypted.json", "");
9403
10249
  if (!name) continue;
9404
- const data = await readEncrypted(join15(servicesDir, file));
10250
+ const data = await readEncrypted(join16(servicesDir, file));
9405
10251
  if (data?.label) legacyServices[name] = data;
9406
10252
  }
9407
- const overridesDir = join15(dbsDir, "overrides");
9408
- const overrideFiles = (await readdir3(overridesDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
10253
+ const overridesDir = join16(dbsDir, "overrides");
10254
+ const overrideFiles = (await readdir4(overridesDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
9409
10255
  const legacyServiceOverrides = {};
9410
10256
  const legacyEnvOverrides = {};
9411
10257
  for (const file of overrideFiles) {
9412
10258
  const baseName = file.replace(".encrypted.json", "");
9413
- const data = await readEncrypted(join15(overridesDir, file));
10259
+ const data = await readEncrypted(join16(overridesDir, file));
9414
10260
  if (!data) continue;
9415
10261
  if (baseName.endsWith("-envs")) {
9416
10262
  legacyServiceOverrides[baseName.slice(0, -5)] = data;
@@ -9418,18 +10264,18 @@ async function runLegacyMigration(dataDir) {
9418
10264
  legacyEnvOverrides[baseName.slice(0, -12)] = data;
9419
10265
  }
9420
10266
  }
9421
- const parsersDir = join15(dbsDir, "parsers");
9422
- const parserFiles = (await readdir3(parsersDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
10267
+ const parsersDir = join16(dbsDir, "parsers");
10268
+ const parserFiles = (await readdir4(parsersDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
9423
10269
  const legacyParsers = [];
9424
10270
  for (const file of parserFiles) {
9425
- const data = await readEncrypted(join15(parsersDir, file));
10271
+ const data = await readEncrypted(join16(parsersDir, file));
9426
10272
  if (data?.id) legacyParsers.push(data);
9427
10273
  }
9428
- const projectMonoId = randomUUID9();
10274
+ const projectMonoId = randomUUID10();
9429
10275
  const envIdMap = {};
9430
10276
  const labelToKey = {};
9431
10277
  for (const [name, legacyEnv] of Object.entries(legacyEnvs)) {
9432
- envIdMap[name] = randomUUID9();
10278
+ envIdMap[name] = randomUUID10();
9433
10279
  if (legacyEnv?.label) labelToKey[legacyEnv.label] = name;
9434
10280
  }
9435
10281
  const runeEnvs = [];
@@ -9454,7 +10300,7 @@ async function runLegacyMigration(dataDir) {
9454
10300
  if (hasInvalidInterpolation(value)) continue;
9455
10301
  const cleaned = cleanVarKey(key);
9456
10302
  if (inheritedVars[cleaned] === value) continue;
9457
- const varId = randomUUID9();
10303
+ const varId = randomUUID10();
9458
10304
  variables[varId] = makeVariable(cleaned, value);
9459
10305
  varIdLookup.set(`${envIdMap[name]}:${cleaned}`, varId);
9460
10306
  }
@@ -9475,7 +10321,7 @@ async function runLegacyMigration(dataDir) {
9475
10321
  const serviceIdMap = {};
9476
10322
  const runeServices = [];
9477
10323
  for (const [name, legacy] of Object.entries(legacyServices)) {
9478
- const id = randomUUID9();
10324
+ const id = randomUUID10();
9479
10325
  serviceIdMap[name] = id;
9480
10326
  const rootPath = legacy.rootPath || "";
9481
10327
  const rootPathCwd = rootPath && rootPath !== "." ? rootPath : "";
@@ -9484,7 +10330,7 @@ async function runLegacyMigration(dataDir) {
9484
10330
  const parts = [cmd.spawnCmd, ...cmd.spawnArgs || []].filter(Boolean);
9485
10331
  if (!parts.length) continue;
9486
10332
  commands.push({
9487
- id: cmd.id || randomUUID9(),
10333
+ id: cmd.id || randomUUID10(),
9488
10334
  label: "Launch",
9489
10335
  command: parts.join(" "),
9490
10336
  cwd: cmd.spawnOptions?.cwd || "",
@@ -9545,7 +10391,7 @@ async function runLegacyMigration(dataDir) {
9545
10391
  const legacyBootstrapCmds = container.bootstrap?.commands || [];
9546
10392
  if (legacyBootstrapCmds.length > 0) {
9547
10393
  dockerConfig.bootstrapCommands = legacyBootstrapCmds.filter((bs) => bs.cmd || bs.entrypoint).map((bs, idx) => ({
9548
- id: bs.id || randomUUID9(),
10394
+ id: bs.id || randomUUID10(),
9549
10395
  label: bs.label || `Bootstrap ${idx + 1}`,
9550
10396
  command: bs.cmd || "",
9551
10397
  ...bs.entrypoint ? { entrypoint: bs.entrypoint } : {},
@@ -9593,7 +10439,7 @@ async function runLegacyMigration(dataDir) {
9593
10439
  cwd: serviceCwd,
9594
10440
  runner: dockerConfig ? "docker" : "native",
9595
10441
  shortcuts: (legacy.shortcuts || []).filter((s) => s.spawnCmd || s.label).map((s) => ({
9596
- id: s.id || randomUUID9(),
10442
+ id: s.id || randomUUID10(),
9597
10443
  label: s.label || s.spawnCmd || "",
9598
10444
  command: [s.spawnCmd, ...s.spawnArgs || []].filter(Boolean).join(" "),
9599
10445
  ...s.spawnOptions?.cwd ? { cwd: s.spawnOptions.cwd } : {}
@@ -9633,7 +10479,7 @@ async function runLegacyMigration(dataDir) {
9633
10479
  );
9634
10480
  if (!svcScopedEnv) {
9635
10481
  svcScopedEnv = {
9636
- id: randomUUID9(),
10482
+ id: randomUUID10(),
9637
10483
  projectId: projectMonoId,
9638
10484
  name: `${service?.name || name} (${globalEnv.name})`,
9639
10485
  scope: "service",
@@ -9657,7 +10503,7 @@ async function runLegacyMigration(dataDir) {
9657
10503
  const varPost = typeof varData === "object" ? String(varData.suffix ?? "") : "";
9658
10504
  if (!varKey || existingKeys.has(cleanVarKey(varKey))) continue;
9659
10505
  const cleaned = cleanVarKey(varKey);
9660
- const varId = randomUUID9();
10506
+ const varId = randomUUID10();
9661
10507
  svcScopedEnv.variables[varId] = makeVariable(cleaned, varValue, varPre, varPost);
9662
10508
  existingKeys.add(cleaned);
9663
10509
  }
@@ -9677,7 +10523,7 @@ async function runLegacyMigration(dataDir) {
9677
10523
  );
9678
10524
  if (!svcScopedEnv) {
9679
10525
  svcScopedEnv = {
9680
- id: randomUUID9(),
10526
+ id: randomUUID10(),
9681
10527
  projectId: projectMonoId,
9682
10528
  name: `${service?.name || serviceName} (${globalEnv.name})`,
9683
10529
  scope: "service",
@@ -9698,7 +10544,7 @@ async function runLegacyMigration(dataDir) {
9698
10544
  const baseEntry = Object.entries(svcScopedEnv.variables).find(([, v]) => v.key === cleaned);
9699
10545
  let varId = baseEntry?.[0];
9700
10546
  if (!varId) {
9701
- varId = randomUUID9();
10547
+ varId = randomUUID10();
9702
10548
  svcScopedEnv.variables[varId] = {
9703
10549
  key: cleaned,
9704
10550
  value: { value: "", pre: "", post: "", isSecret: false }
@@ -9728,13 +10574,13 @@ async function runLegacyMigration(dataDir) {
9728
10574
  flatOverrides[varId] = makeVariable(cleaned, value);
9729
10575
  }
9730
10576
  } else {
9731
- flatOverrides[randomUUID9()] = makeVariable(cleaned, value);
10577
+ flatOverrides[randomUUID10()] = makeVariable(cleaned, value);
9732
10578
  }
9733
10579
  } else {
9734
10580
  const cleaned = cleanVarKey(key);
9735
10581
  const existingVarId = Object.entries(env2.variables).find(([, v]) => v.key === cleaned)?.[0];
9736
10582
  if (!existingVarId) {
9737
- const varId = randomUUID9();
10583
+ const varId = randomUUID10();
9738
10584
  env2.variables[varId] = makeVariable(cleaned, value);
9739
10585
  varIdLookup.set(`${envId}:${cleaned}`, varId);
9740
10586
  }
@@ -9763,7 +10609,7 @@ async function runLegacyMigration(dataDir) {
9763
10609
  }
9764
10610
  let existingSettings = {};
9765
10611
  try {
9766
- existingSettings = JSON.parse(await readFile11(join15(dataDir, "settings.json"), "utf-8"));
10612
+ existingSettings = JSON.parse(await readFile12(join16(dataDir, "settings.json"), "utf-8"));
9767
10613
  } catch {
9768
10614
  }
9769
10615
  const migratedParsers = legacyParsers.map((p) => ({
@@ -9784,11 +10630,11 @@ ${p.transform ?? ""}`,
9784
10630
  ...migratedParsers
9785
10631
  ]
9786
10632
  };
9787
- await atomicWrite(join15(dataDir, "projects.json"), runeProjects);
9788
- await atomicWrite(join15(dataDir, "services.json"), runeServices);
9789
- await atomicWrite(join15(dataDir, "environments.json"), runeEnvs);
9790
- await atomicWrite(join15(dataDir, "environment-overrides.json"), flatOverrides);
9791
- await atomicWrite(join15(dataDir, "settings.json"), newSettings);
10633
+ await atomicWrite(join16(dataDir, "projects.json"), runeProjects);
10634
+ await atomicWrite(join16(dataDir, "services.json"), runeServices);
10635
+ await atomicWrite(join16(dataDir, "environments.json"), runeEnvs);
10636
+ await atomicWrite(join16(dataDir, "environment-overrides.json"), flatOverrides);
10637
+ await atomicWrite(join16(dataDir, "settings.json"), newSettings);
9792
10638
  const svcEnvCount = runeEnvs.filter((e) => e.scope === "service").length;
9793
10639
  console.log(`[legacy-migration] Written: ${runeProjects.length} projects, ${runeServices.length} services, ${runeEnvs.length} environments (${svcEnvCount} service-scoped)`);
9794
10640
  console.log(`[legacy-migration] Overrides: ${Object.keys(flatOverrides).length}, custom parsers: ${migratedParsers.length} (all disabled \u2014 rewrite required)`);
@@ -9799,12 +10645,12 @@ ${p.transform ?? ""}`,
9799
10645
  }
9800
10646
  async function findLegacyKeyInBackup(dataDir) {
9801
10647
  try {
9802
- const entries = await readdir3(dataDir);
10648
+ const entries = await readdir4(dataDir);
9803
10649
  const backupDirs = entries.filter((e) => e.startsWith("backup-")).sort().reverse();
9804
10650
  for (const dir of backupDirs) {
9805
- const keyJsonPath = join15(dataDir, dir, "dbs", "encryption-key.json");
10651
+ const keyJsonPath = join16(dataDir, dir, "dbs", "encryption-key.json");
9806
10652
  try {
9807
- const raw = await readFile11(keyJsonPath, "utf-8");
10653
+ const raw = await readFile12(keyJsonPath, "utf-8");
9808
10654
  const parsed = JSON.parse(raw);
9809
10655
  if (!parsed.encryptionKey) continue;
9810
10656
  const sodium = await getSodium();
@@ -9826,14 +10672,14 @@ async function runOverrideOnlyMigration(dataDir) {
9826
10672
  const abDataDir = resolve5(dataDir);
9827
10673
  const abCwd = resolve5(process.cwd());
9828
10674
  const candidateDirs = [
9829
- join15(abCwd, ".runeya", "dbs", "overrides"),
9830
- join15(abDataDir, "dbs", "overrides")
10675
+ join16(abCwd, ".runeya", "dbs", "overrides"),
10676
+ join16(abDataDir, "dbs", "overrides")
9831
10677
  ];
9832
10678
  let overridesDir = null;
9833
10679
  let overrideFiles = [];
9834
10680
  for (const dir of candidateDirs) {
9835
10681
  try {
9836
- const files = (await readdir3(dir)).filter((f) => f.endsWith(".encrypted.json"));
10682
+ const files = (await readdir4(dir)).filter((f) => f.endsWith(".encrypted.json"));
9837
10683
  if (files.length > 0) {
9838
10684
  overridesDir = dir;
9839
10685
  overrideFiles = files;
@@ -9843,9 +10689,9 @@ async function runOverrideOnlyMigration(dataDir) {
9843
10689
  }
9844
10690
  }
9845
10691
  if (!overridesDir || overrideFiles.length === 0) return false;
9846
- const legacyKeyPath = join15(overridesDir, "..", "encryption-key.json");
10692
+ const legacyKeyPath = join16(overridesDir, "..", "encryption-key.json");
9847
10693
  try {
9848
- await access(legacyKeyPath);
10694
+ await access2(legacyKeyPath);
9849
10695
  return false;
9850
10696
  } catch {
9851
10697
  }
@@ -9861,7 +10707,7 @@ async function runOverrideOnlyMigration(dataDir) {
9861
10707
  const envNameToId = {};
9862
10708
  let envsRaw;
9863
10709
  try {
9864
- envsRaw = JSON.parse(await readFile11(join15(abDataDir, "environments.json"), "utf-8"));
10710
+ envsRaw = JSON.parse(await readFile12(join16(abDataDir, "environments.json"), "utf-8"));
9865
10711
  for (const _env of envsRaw) {
9866
10712
  const env2 = _env;
9867
10713
  if (env2["name"] && env2["id"]) {
@@ -9880,7 +10726,7 @@ async function runOverrideOnlyMigration(dataDir) {
9880
10726
  let servicesRaw = [];
9881
10727
  const serviceNameToId = {};
9882
10728
  try {
9883
- servicesRaw = JSON.parse(await readFile11(join15(abDataDir, "services.json"), "utf-8"));
10729
+ servicesRaw = JSON.parse(await readFile12(join16(abDataDir, "services.json"), "utf-8"));
9884
10730
  for (const _svc of servicesRaw) {
9885
10731
  const svc = _svc;
9886
10732
  if (svc["name"] && svc["id"]) serviceNameToId[svc["name"]] = svc["id"];
@@ -9897,7 +10743,7 @@ async function runOverrideOnlyMigration(dataDir) {
9897
10743
  const envName = baseName.slice(0, -"-environment".length);
9898
10744
  let data;
9899
10745
  try {
9900
- data = await decryptLegacyFile(join15(overridesDir, file), sodiumKey, sodium);
10746
+ data = await decryptLegacyFile(join16(overridesDir, file), sodiumKey, sodium);
9901
10747
  } catch (err) {
9902
10748
  console.warn(`[override-migration] Failed to decrypt ${file}: ${err.message}`);
9903
10749
  continue;
@@ -9919,12 +10765,12 @@ async function runOverrideOnlyMigration(dataDir) {
9919
10765
  if (isSameAsBase(value, baseVar)) continue;
9920
10766
  flatOverrides[varId] = makeVariable(cleaned, value);
9921
10767
  } else {
9922
- flatOverrides[randomUUID9()] = makeVariable(cleaned, value);
10768
+ flatOverrides[randomUUID10()] = makeVariable(cleaned, value);
9923
10769
  }
9924
10770
  }
9925
10771
  }
9926
10772
  if (Object.keys(flatOverrides).length > 0) {
9927
- await atomicWrite(join15(abDataDir, "environment-overrides.json"), flatOverrides);
10773
+ await atomicWrite(join16(abDataDir, "environment-overrides.json"), flatOverrides);
9928
10774
  console.log(`[override-migration] Written ${Object.keys(flatOverrides).length} override(s) to environment-overrides.json`);
9929
10775
  }
9930
10776
  let envsModified = false;
@@ -9934,7 +10780,7 @@ async function runOverrideOnlyMigration(dataDir) {
9934
10780
  const serviceName = baseName.slice(0, -"-envs".length);
9935
10781
  let data;
9936
10782
  try {
9937
- data = await decryptLegacyFile(join15(overridesDir, file), sodiumKey, sodium);
10783
+ data = await decryptLegacyFile(join16(overridesDir, file), sodiumKey, sodium);
9938
10784
  } catch (err) {
9939
10785
  console.warn(`[override-migration] Failed to decrypt ${file}: ${err.message}`);
9940
10786
  continue;
@@ -9956,7 +10802,7 @@ async function runOverrideOnlyMigration(dataDir) {
9956
10802
  );
9957
10803
  if (!svcScopedEnv) {
9958
10804
  svcScopedEnv = {
9959
- id: randomUUID9(),
10805
+ id: randomUUID10(),
9960
10806
  projectId: globalEnv["projectId"],
9961
10807
  name: `${service?.["name"] || serviceName} (${globalEnv["name"]})`,
9962
10808
  scope: "service",
@@ -9978,7 +10824,7 @@ async function runOverrideOnlyMigration(dataDir) {
9978
10824
  const baseEntry = Object.entries(svcScopedEnv["variables"]).find(([, v]) => v.key === cleaned);
9979
10825
  let varId = baseEntry?.[0];
9980
10826
  if (!varId) {
9981
- varId = randomUUID9();
10827
+ varId = randomUUID10();
9982
10828
  svcScopedEnv["variables"][varId] = {
9983
10829
  key: cleaned,
9984
10830
  value: { value: "", pre: "", post: "", isSecret: false }
@@ -9993,7 +10839,7 @@ async function runOverrideOnlyMigration(dataDir) {
9993
10839
  }
9994
10840
  }
9995
10841
  if (envsModified) {
9996
- await atomicWrite(join15(abDataDir, "environments.json"), envsRaw);
10842
+ await atomicWrite(join16(abDataDir, "environments.json"), envsRaw);
9997
10843
  console.log("[override-migration] Merged service overrides into environments.json");
9998
10844
  }
9999
10845
  const migratedDir = overridesDir.replace(/overrides$/, "overrides-migrated");
@@ -10017,7 +10863,7 @@ var ARGON2_OPTIONS = { memoryCost: 65536, timeCost: 3, parallelism: 4, outputLen
10017
10863
  async function readKeyFile(filePath) {
10018
10864
  const s = await stat4(filePath);
10019
10865
  if (s.size > MAX_KEY_FILE_SIZE) throw new Error(`Fichier suspect : ${filePath} (${s.size} octets)`);
10020
- return readFile12(filePath);
10866
+ return readFile13(filePath);
10021
10867
  }
10022
10868
  function aesEncrypt(key, plaintext) {
10023
10869
  const iv = randomBytes4(12);
@@ -10036,7 +10882,7 @@ function aesDecrypt(key, data) {
10036
10882
  }
10037
10883
  async function getPepperFromFile(dataDir) {
10038
10884
  try {
10039
- const raw = await readKeyFile(join16(dataDir, PEPPER_FILENAME));
10885
+ const raw = await readKeyFile(join17(dataDir, PEPPER_FILENAME));
10040
10886
  const hex = raw.toString("utf-8").trim();
10041
10887
  if (/^[0-9a-f]{64}$/i.test(hex)) return Buffer.from(hex, "hex");
10042
10888
  } catch {
@@ -10044,7 +10890,7 @@ async function getPepperFromFile(dataDir) {
10044
10890
  return null;
10045
10891
  }
10046
10892
  async function writePepperToFile(dataDir, pepper) {
10047
- const filePath = join16(dataDir, PEPPER_FILENAME);
10893
+ const filePath = join17(dataDir, PEPPER_FILENAME);
10048
10894
  const tmpPath = filePath + ".tmp";
10049
10895
  await writeFile12(tmpPath, pepper.toString("hex"), "utf-8");
10050
10896
  await chmod9(tmpPath, 384);
@@ -10095,14 +10941,14 @@ async function writeEncryptedKey(dataDir, masterPassword, encryptionKey) {
10095
10941
  const payload = aesEncrypt(derivedKey, encryptionKey);
10096
10942
  const fileContent = Buffer.concat([salt, payload]);
10097
10943
  const encoded = fileContent.toString("base64");
10098
- const filePath = join16(dataDir, ENCRYPTED_KEY_FILENAME);
10944
+ const filePath = join17(dataDir, ENCRYPTED_KEY_FILENAME);
10099
10945
  const tmpPath = filePath + ".tmp";
10100
10946
  await writeFile12(tmpPath, encoded, "utf-8");
10101
10947
  await chmod9(tmpPath, 384);
10102
10948
  await rename10(tmpPath, filePath);
10103
10949
  }
10104
10950
  async function readEncryptedKey(dataDir, masterPassword) {
10105
- const filePath = join16(dataDir, ENCRYPTED_KEY_FILENAME);
10951
+ const filePath = join17(dataDir, ENCRYPTED_KEY_FILENAME);
10106
10952
  const raw = await readKeyFile(filePath);
10107
10953
  const fileContent = Buffer.from(raw.toString("utf-8").trim(), "base64");
10108
10954
  const salt = fileContent.subarray(0, 16);
@@ -10122,13 +10968,13 @@ async function getPepperFromKeychain(dataDir) {
10122
10968
  }
10123
10969
  async function writeVerifyFile(dataDir, encryptionKey) {
10124
10970
  const payload = aesEncrypt(encryptionKey, Buffer.from(CANARY_PLAINTEXT, "utf-8"));
10125
- const filePath = join16(dataDir, VERIFY_FILENAME);
10971
+ const filePath = join17(dataDir, VERIFY_FILENAME);
10126
10972
  const tmpPath = filePath + ".tmp";
10127
10973
  await writeFile12(tmpPath, payload.toString("base64"), "utf-8");
10128
10974
  await rename10(tmpPath, filePath);
10129
10975
  }
10130
10976
  async function verifyEncryptionKey(dataDir, encryptionKey) {
10131
- const filePath = join16(dataDir, VERIFY_FILENAME);
10977
+ const filePath = join17(dataDir, VERIFY_FILENAME);
10132
10978
  const raw = await readKeyFile(filePath);
10133
10979
  const payload = Buffer.from(raw.toString("utf-8").trim(), "base64");
10134
10980
  const plaintext = aesDecrypt(encryptionKey, payload);
@@ -10138,7 +10984,7 @@ async function verifyEncryptionKey(dataDir, encryptionKey) {
10138
10984
  }
10139
10985
  async function hasVerifyFile(dataDir) {
10140
10986
  try {
10141
- await stat4(join16(dataDir, VERIFY_FILENAME));
10987
+ await stat4(join17(dataDir, VERIFY_FILENAME));
10142
10988
  return true;
10143
10989
  } catch {
10144
10990
  return false;
@@ -10146,7 +10992,7 @@ async function hasVerifyFile(dataDir) {
10146
10992
  }
10147
10993
  async function hasEncryptedKeyFile(dataDir) {
10148
10994
  try {
10149
- await stat4(join16(dataDir, ENCRYPTED_KEY_FILENAME));
10995
+ await stat4(join17(dataDir, ENCRYPTED_KEY_FILENAME));
10150
10996
  return true;
10151
10997
  } catch {
10152
10998
  return false;
@@ -10160,17 +11006,17 @@ async function getServerState() {
10160
11006
  let hasVerify = false;
10161
11007
  let hasData = false;
10162
11008
  try {
10163
- await stat5(join17(dataDir, ENCRYPTED_KEY_FILENAME2));
11009
+ await stat5(join18(dataDir, ENCRYPTED_KEY_FILENAME2));
10164
11010
  hasEncFile = true;
10165
11011
  } catch {
10166
11012
  }
10167
11013
  try {
10168
- await stat5(join17(dataDir, VERIFY_FILENAME2));
11014
+ await stat5(join18(dataDir, VERIFY_FILENAME2));
10169
11015
  hasVerify = true;
10170
11016
  } catch {
10171
11017
  }
10172
11018
  try {
10173
- await stat5(join17(dataDir, "projects.json"));
11019
+ await stat5(join18(dataDir, "projects.json"));
10174
11020
  hasData = true;
10175
11021
  } catch {
10176
11022
  }
@@ -10613,6 +11459,9 @@ async function createLocalServer() {
10613
11459
  }
10614
11460
  await agentManager.init();
10615
11461
  await agentSpawner.spawnLocalAgent();
11462
+ startClaudeTranscriptWatcher().catch((err) => {
11463
+ console.warn("[server] Failed to start Claude transcript watcher:", err);
11464
+ });
10616
11465
  }
10617
11466
  if (hasEncryptionKey()) {
10618
11467
  await runPostUnlock();
@@ -11020,6 +11869,11 @@ async function pullEnv(opts) {
11020
11869
  return Object.entries(env2).map(([key, value]) => `${key}=${value}`).join("\n");
11021
11870
  }
11022
11871
  dotenv.config({ quiet: true });
11872
+ if (process.env["DATA_DIR"]) {
11873
+ const resolved = resolvePath(process.env["DATA_DIR"], "..");
11874
+ process.chdir(resolved);
11875
+ console.log(`[server] Working directory: ${resolved} (from DATA_DIR)`);
11876
+ }
11023
11877
  function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500) {
11024
11878
  return new Promise((resolve5, reject) => {
11025
11879
  let attempts = 0;
@@ -11089,4 +11943,4 @@ export {
11089
11943
  createLocalServer,
11090
11944
  pullEnv
11091
11945
  };
11092
- //# sourceMappingURL=dist-HZBWBT6S.js.map
11946
+ //# sourceMappingURL=dist-2H7XZQCV.js.map