@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.
- package/agent/index.js +354 -8
- package/agent/index.js.map +1 -1
- package/agent-manager-V47DLMYP-26VYXTSG.js +8 -0
- package/{chunk-DODGBBTE.js → chunk-6547GLUJ.js} +24 -7
- package/chunk-6547GLUJ.js.map +1 -0
- package/{dist-HZBWBT6S.js → dist-2H7XZQCV.js} +1233 -379
- package/dist-2H7XZQCV.js.map +1 -0
- package/index.js +5 -1
- package/index.js.map +1 -1
- package/package.json +1 -1
- package/web/assets/AddressBar.vue-BxQkuTJ-.js +1 -0
- package/web/assets/AgentScalarChatInterface.vue-BZyh4z8j.js +13 -0
- package/web/assets/AgentTerminalPage-SWnqcTm-.js +1 -0
- package/web/assets/AgentTerminalPage-anYDkj7H.css +1 -0
- package/web/assets/AiChatPage-PJvieNVr.js +1 -0
- package/web/assets/AppLayout-qD2ReaTx.js +1 -0
- package/web/assets/AuditPage-DCvW5-HA.js +1 -0
- package/web/assets/CodeInput.vue-DDs5Eel7.js +1 -0
- package/web/assets/Collection.vue-HH2Z976J.js +1 -0
- package/web/assets/CollectionAuthentication.vue-BbsKOTJ9.js +1 -0
- package/web/assets/CollectionCookies.vue-Dzlq0l8S.js +1 -0
- package/web/assets/CollectionEnvironment.vue-BS9B1OU-.js +1 -0
- package/web/assets/CollectionOverview.vue-DXXQHn9J.js +1 -0
- package/web/assets/CollectionScripts.vue-TVNOxHuu.js +1 -0
- package/web/assets/CollectionServers.vue-DDyH4n80.js +1 -0
- package/web/assets/CollectionSettings.vue-BD5QvG5c.js +1 -0
- package/web/assets/CollectionSync.vue-g_NhkdXC.js +1 -0
- package/web/assets/CommandActionInput.vue-Cot3fJDd.js +1 -0
- package/web/assets/Cookies.vue-X8bc_7ud.js +1 -0
- package/web/assets/DashboardPage-BuP2oOo3.js +1 -0
- package/web/assets/DataTable.vue-DmcNIRcD.js +1 -0
- package/web/assets/DataTableCheckbox.vue-B7bvOQ_f.js +1 -0
- package/web/assets/{DataTableHeader.vue-U2le7KBO.js → DataTableHeader.vue-D5k0UCfl.js} +1 -1
- package/web/assets/DataTableInput.vue-DVUCY60y.js +1 -0
- package/web/assets/DeleteSidebarListElement.vue-CAri6AWj.js +1 -0
- package/web/assets/EditSidebarListElement.vue-DFY1ZZNE.js +1 -0
- package/web/assets/{EditorIcon-BPEaZrXC.js → EditorIcon-DOV6wP8X.js} +2 -2
- package/web/assets/{EmptyState.vue-Be8_w2rR.js → EmptyState.vue-watFzTsD.js} +2 -2
- package/web/assets/Environment.vue-BbpisYLs.js +1 -0
- package/web/assets/EnvironmentForm-CyJtNH2k.js +108 -0
- package/web/assets/EnvironmentFormPage-Du4EXZlm.js +1 -0
- package/web/assets/EnvironmentListPage-1Jdh0AWO.js +1 -0
- package/web/assets/EnvironmentModal.vue-DaI310h8.js +1 -0
- package/web/assets/EnvironmentSelector-BPhBcKYj.js +1 -0
- package/web/assets/Form.vue-CMWUiD6f.js +1 -0
- package/web/assets/IconSelector.vue-90PWhHNq.js +1 -0
- package/web/assets/{KanbanBoardPage-7rLK-ucb.js → KanbanBoardPage-DzFRgXlJ.js} +6 -6
- package/web/assets/KanbanListPage-O3T-L6z3.js +1 -0
- package/web/assets/LaunchProjectPage-B2m_PflY.js +1 -0
- package/web/assets/LaunchServicesPage-DgfE9971.js +1 -0
- package/web/assets/LogViewer-CXVXsymr.js +3 -0
- package/web/assets/ProcessControls-DVQwL-qx.js +1 -0
- package/web/assets/ProcessDetailPage-8GtSwfXG.js +1 -0
- package/web/assets/{ProcessMetrics-CLa8mdh2.js → ProcessMetrics-9RjmvDOk.js} +2 -2
- package/web/assets/ProjectDetailPage-B3s8bbmw.js +1 -0
- package/web/assets/ProjectListPage-HQ9gZWHw.js +1 -0
- package/web/assets/RandomJobTestPage-DAM5nNLf.js +2 -0
- package/web/assets/Request.vue-DiF4lAAD.js +1 -0
- package/web/assets/RequestAuth.vue-DHU9RNUf.js +1 -0
- package/web/assets/RequestRoot.vue-BiM2Tr20.js +19 -0
- package/web/assets/ScalarAsciiArt.vue-2vz_9aEa.js +2 -0
- package/web/assets/{ScalarIconEyeSlash.vue-qR90sHHe.js → ScalarIconEyeSlash.vue-CwJnZ-zD.js} +1 -1
- package/web/assets/{ScalarIconTrash.vue-Ll6Zw6_W.js → ScalarIconTrash.vue-DAEOI9Kk.js} +1 -1
- package/web/assets/ScenarioEditorPage-BzPL_7Up.js +14 -0
- package/web/assets/ServerVariablesForm.vue-Dj2R8IEV.js +1 -0
- package/web/assets/ServiceControlsBar-CsHZTuPK.js +1 -0
- package/web/assets/ServiceDetailPage-Csf55wQD.js +2 -0
- package/web/assets/ServiceFormPage-DDJYv2z3.js +1 -0
- package/web/assets/Settings.vue-Dyzw_xwE.js +1 -0
- package/web/assets/SettingsPage-CYQGpGyw.js +225 -0
- package/web/assets/SetupKeyPage-CnaFhUR6.js +1 -0
- package/web/assets/SetupPage-sqBZmTQ9.js +1 -0
- package/web/assets/Sidebar.vue-DqiliUT_.js +1 -0
- package/web/assets/SidebarButton.vue-MCBoC3jO.js +1 -0
- package/web/assets/SidebarListElement.vue-DswWWg_S.js +1 -0
- package/web/assets/UnlockPage-rDy89SdE.js +1 -0
- package/web/assets/VariablesTable-CEaksHyg.js +1 -0
- package/web/assets/{VariablesTable-BtdK5olT.css → VariablesTable-W4rxlL35.css} +1 -1
- package/web/assets/ViewLayout.vue-CgpeQh3K.js +1 -0
- package/web/assets/{ViewLayoutContent.vue-CAixUN8B.js → ViewLayoutContent.vue-DP_GK_Ip.js} +1 -1
- package/web/assets/ViewLayoutSection.vue-YQYzkcAB.js +1 -0
- package/web/assets/active-entities-CWFFm964.js +1 -0
- package/web/assets/api-reference-UJl6P2J4.js +1 -0
- package/web/assets/{autocomplete-Do-EC8XW.js → autocomplete-CK9Z1-Wz.js} +4 -4
- package/web/assets/card-DadsU5ZI.js +32 -0
- package/web/assets/{checkbox-C2hHZbMO.js → checkbox-4z5ftHhs.js} +3 -3
- package/web/assets/{chip-CD3EONN7.js → chip-KJbF4Jjk.js} +2 -2
- package/web/assets/column-Cp1kVQOt.js +763 -0
- package/web/assets/core-7y546zlP.js +1 -0
- package/web/assets/{cssMode-BzsFOrHs.js → cssMode-CIUATcSv.js} +1 -1
- package/web/assets/dist-BLSATgV0.js +1 -0
- package/web/assets/{dist-CiBpb7F_.js → dist-Cp1hgSqY.js} +2 -2
- package/web/assets/{dist-BmCpMB7W.js → dist-CpE_ZYIQ.js} +1 -1
- package/web/assets/dist-DR4LqG1l.js +192 -0
- package/web/assets/{divider-C7F2jaMI.js → divider-D-vBLTer.js} +2 -2
- package/web/assets/editor.store-COpS72tz.js +1 -0
- package/web/assets/{fieldset-kJDAoVaz.js → fieldset-CSlJxjpZ.js} +2 -2
- package/web/assets/{freemarker2-qyf3DfQl.js → freemarker2-Dsxb69Av.js} +1 -1
- package/web/assets/{handlebars-Dt_j38Ok.js → handlebars-ChbvHuc-.js} +1 -1
- package/web/assets/{html-BM6JBSDe.js → html-DOIF2Zpc.js} +1 -1
- package/web/assets/{htmlMode-BWrj-eTf.js → htmlMode-DKICvQwb.js} +1 -1
- package/web/assets/index-A6eiSk8H.css +1 -0
- package/web/assets/{index-Dviyssba.js → index-C0UzL9Y-.js} +281 -246
- package/web/assets/{javascript-Nwj2LVeE.js → javascript-Bmt68E8X.js} +1 -1
- package/web/assets/{jsonMode-xQ2R28Fq.js → jsonMode-CYcvn9c0.js} +1 -1
- package/web/assets/{keyboard-CidI6qyD.js → keyboard-DZklF_AY.js} +2 -2
- package/web/assets/{lib-Bpt_o5V9.js → lib-B5TdN_ca.js} +1 -1
- package/web/assets/{library-CcEqwrWR.js → library-DtrGq8p8.js} +1 -1
- package/web/assets/{liquid-CNc1r-Ln.js → liquid-3ot_oRqd.js} +1 -1
- package/web/assets/{mdx-D5yTipSh.js → mdx-Cm1rlDNM.js} +1 -1
- package/web/assets/{mediaTypes-pLZCmXiT.js → mediaTypes-Bz4C7Sle.js} +2 -2
- package/web/assets/{message-B6M86BRU.js → message-DFReN7Fq.js} +2 -2
- package/web/assets/{minus-wesZvWq4.js → minus-t_EQEzUj.js} +2 -2
- package/web/assets/{multiselect-CHEmtEFg.js → multiselect-BZbgA1pl.js} +3 -3
- package/web/assets/{password-ISam7EYg.js → password-D82un95s.js} +4 -4
- package/web/assets/{plus-BBMmFC_c.js → plus-B-AhHpHU.js} +6 -6
- package/web/assets/pretty-ms-XBeIOdhN.js +202 -0
- package/web/assets/process.store-jv0SI_6q.js +1 -0
- package/web/assets/project.store-DHXLE6Lv.js +1 -0
- package/web/assets/{python-BOLsEvIQ.js → python-BuQCsdKw.js} +1 -1
- package/web/assets/{radiobutton-DwoOAEAF.js → radiobutton-Btattklk.js} +2 -2
- package/web/assets/{razor-JsBDv4HY.js → razor-DgnEPQLs.js} +1 -1
- package/web/assets/router-w5lyb-nn.js +1 -0
- package/web/assets/settings.store-bNnLJ43c.js +1 -0
- package/web/assets/store-DWOu5TTl.js +1840 -0
- package/web/assets/{tabpanel-DBInUEl5.js → tabpanel-CIyzJ44J.js} +3 -3
- package/web/assets/{tag-CQlI4fJK.js → tag-DV-szbCr.js} +2 -2
- package/web/assets/trpc-wp_QsEqG.js +1 -0
- package/web/assets/{tsMode-C7uad5vV.js → tsMode-ChD0IpIg.js} +1 -1
- package/web/assets/{typescript-C2IozRX9.js → typescript-BRYpIqKe.js} +1 -1
- package/web/assets/urls-CoBMqtDH.js +12 -0
- package/web/assets/{use-tree-walker-CJHEPXgB.js → use-tree-walker-M4-Gn01v.js} +1 -1
- package/web/assets/useAiChatDrawer-BhlTNoXz.js +1 -0
- package/web/assets/{useClipboard-DtO-z5Ls.js → useClipboard-Cro2wlg3.js} +1 -1
- package/web/assets/{useLayout-D-FCdkqa.js → useLayout-BciG4r-1.js} +1 -1
- package/web/assets/{usePluginManager-CPTBi3rk.js → usePluginManager-BonTfGSN.js} +1 -1
- package/web/assets/useSidebar-77_u4hef.js +1 -0
- package/web/assets/{vue-router-DSROI5EH.js → vue-router-DdBypSuY.js} +1 -1
- package/web/assets/{xml-DtJwg-5S.js → xml-Cz29Bxul.js} +1 -1
- package/web/assets/{yaml-sPGHMYDm.js → yaml-CiZl2tNW.js} +1 -1
- package/web/index.html +2 -2
- package/agent-manager-A4E5CXFT-6PJDFN7J.js +0 -8
- package/chunk-DODGBBTE.js.map +0 -1
- package/dist-HZBWBT6S.js.map +0 -1
- package/web/assets/AddressBar.vue-D_8vQPIF.js +0 -1
- package/web/assets/AgentScalarChatInterface.vue-Be-LbA03.js +0 -13
- package/web/assets/AgentTerminalPage-BZmmd1H1.js +0 -36
- package/web/assets/AgentTerminalPage-EX_n6P3x.css +0 -1
- package/web/assets/AiChatPage-kTk6yC6d.js +0 -1
- package/web/assets/AppLayout-jbNML_m4.js +0 -1
- package/web/assets/AuditPage-S4kDjrZ5.js +0 -1
- package/web/assets/CodeInput.vue-nIhSM0FX.js +0 -1
- package/web/assets/Collection.vue-CkwnxBu4.js +0 -1
- package/web/assets/CollectionAuthentication.vue-CiZinGpr.js +0 -1
- package/web/assets/CollectionCookies.vue-BLEJqAhX.js +0 -1
- package/web/assets/CollectionEnvironment.vue-3bWtuG1B.js +0 -1
- package/web/assets/CollectionOverview.vue-BheZFfD3.js +0 -1
- package/web/assets/CollectionScripts.vue-CsY6tWxk.js +0 -1
- package/web/assets/CollectionServers.vue-Ja8mYDaK.js +0 -1
- package/web/assets/CollectionSettings.vue-DOB9JUg8.js +0 -1
- package/web/assets/CollectionSync.vue-B-uKwdTU.js +0 -1
- package/web/assets/CommandActionInput.vue-BBijVqe7.js +0 -1
- package/web/assets/Cookies.vue-DgVzSolJ.js +0 -1
- package/web/assets/DashboardPage-BwZPpURp.js +0 -1
- package/web/assets/DataTable.vue-DXTWz2Vi.js +0 -1
- package/web/assets/DataTableCheckbox.vue-BA5yHmxd.js +0 -1
- package/web/assets/DataTableInput.vue-DpeNUUXc.js +0 -1
- package/web/assets/DeleteSidebarListElement.vue-C2SV2geU.js +0 -1
- package/web/assets/EditSidebarListElement.vue-DvvYjTMO.js +0 -1
- package/web/assets/Environment.vue-D0PdeVIp.js +0 -1
- package/web/assets/EnvironmentForm-Dm3dFrSU.js +0 -108
- package/web/assets/EnvironmentFormPage-ByVELlly.js +0 -1
- package/web/assets/EnvironmentListPage-DvaTK4NF.js +0 -1
- package/web/assets/EnvironmentModal.vue-Bssf0U5X.js +0 -1
- package/web/assets/EnvironmentSelector-4kpQpg5e.js +0 -1
- package/web/assets/Form.vue-Dv4a31zh.js +0 -1
- package/web/assets/IconSelector.vue-BsYvff6K.js +0 -1
- package/web/assets/KanbanListPage-Dn35VrDc.js +0 -1
- package/web/assets/LaunchProjectPage-Byemtd9C.js +0 -1
- package/web/assets/LaunchServicesPage-CHiV6T5Y.js +0 -1
- package/web/assets/LogViewer-KtrKiUZw.js +0 -3
- package/web/assets/ProcessControls-DfFxf6jZ.js +0 -1
- package/web/assets/ProcessDetailPage-BOI85Xo7.js +0 -1
- package/web/assets/ProjectDetailPage-CTEt0DSq.js +0 -1
- package/web/assets/ProjectListPage-BrTIxp9K.js +0 -1
- package/web/assets/RandomJobTestPage-D2OB5pqD.js +0 -2
- package/web/assets/Request.vue-DHQd9eyp.js +0 -1
- package/web/assets/RequestAuth.vue-CEVOgzEk.js +0 -1
- package/web/assets/RequestRoot.vue-BEtC7IEP.js +0 -19
- package/web/assets/ScalarAsciiArt.vue-CyYmv9BB.js +0 -2
- package/web/assets/ScenarioEditorPage-xCx9aW1X.js +0 -14
- package/web/assets/ServerVariablesForm.vue-D9sjD4AT.js +0 -1
- package/web/assets/ServiceControlsBar-DKYIgScL.js +0 -1
- package/web/assets/ServiceDetailPage-B5qBCc9A.js +0 -2
- package/web/assets/ServiceFormPage-BNpWmyj_.js +0 -1
- package/web/assets/Settings.vue-DWPIjz57.js +0 -1
- package/web/assets/SettingsPage-C5lWJT0Z.js +0 -225
- package/web/assets/SetupKeyPage-BiqO4PQr.js +0 -1
- package/web/assets/SetupPage-FEUuQSQ2.js +0 -1
- package/web/assets/Sidebar.vue-DVHNacfA.js +0 -1
- package/web/assets/SidebarButton.vue-C4QDgSym.js +0 -1
- package/web/assets/SidebarListElement.vue-Ch1aI_Bz.js +0 -1
- package/web/assets/UnlockPage-Dhg0hoP-.js +0 -1
- package/web/assets/VariablesTable-DFJHJ9DF.js +0 -1
- package/web/assets/ViewLayout.vue-svdAMKVP.js +0 -1
- package/web/assets/ViewLayoutSection.vue-C9VAyihF.js +0 -1
- package/web/assets/active-entities-Dpd4ny38.js +0 -1
- package/web/assets/api-reference-C9jD1TQ9.js +0 -1
- package/web/assets/card-CeTOcGyS.js +0 -32
- package/web/assets/column-xfOef4Rh.js +0 -763
- package/web/assets/core-Dq8cnTTd.js +0 -1
- package/web/assets/dist-CQoPOD_o.js +0 -192
- package/web/assets/dist-US_vG96F.js +0 -1
- package/web/assets/editor.store-Dzhoqquh.js +0 -1
- package/web/assets/index-DXutsd9M.css +0 -1
- package/web/assets/pretty-ms-Bj2IaA2r.js +0 -202
- package/web/assets/process.store-cvjJzD3H.js +0 -1
- package/web/assets/project.store-C94-7i6c.js +0 -1
- package/web/assets/router-BgHCboHx.js +0 -1
- package/web/assets/settings.store-BGScNTnu.js +0 -1
- package/web/assets/store-a88gBDkh.js +0 -1840
- package/web/assets/trpc-CgAubgRH.js +0 -1
- package/web/assets/urls-BuNgEu0U.js +0 -12
- package/web/assets/useAiChatDrawer-D6TRHaNu.js +0 -1
- package/web/assets/useSidebar-BoK12gD1.js +0 -1
- /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-
|
|
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 {
|
|
125
|
-
import {
|
|
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
|
|
134
|
-
import { join as
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
161
|
-
import { join as
|
|
162
|
-
import { randomUUID as
|
|
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
|
|
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
|
|
186
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
180
187
|
import {
|
|
181
|
-
readFile as
|
|
188
|
+
readFile as readFile12,
|
|
182
189
|
writeFile as writeFile11,
|
|
183
190
|
rename as rename9,
|
|
184
191
|
mkdir as mkdir13,
|
|
185
|
-
access,
|
|
186
|
-
readdir as
|
|
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
|
|
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
|
-
|
|
3414
|
-
|
|
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
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
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
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
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
|
|
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(
|
|
4391
|
-
await atomicWriteSecure(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
5309
|
-
const { agentManager: agentManager2 } = await import("./agent-manager-
|
|
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
|
|
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-${
|
|
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 =
|
|
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(
|
|
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 =
|
|
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:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
9361
|
-
const legacyKeyPath =
|
|
10206
|
+
const legacyRoot = join16(abCwd, env.DATA_DIR);
|
|
10207
|
+
const legacyKeyPath = join16(legacyRoot, "dbs", "encryption-key.json");
|
|
9362
10208
|
try {
|
|
9363
|
-
await
|
|
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 =
|
|
10215
|
+
const legacyTmp = join16(abCwd, ".runeya-tmp");
|
|
9370
10216
|
await rename9(legacyRoot, legacyTmp);
|
|
9371
10217
|
await mkdir13(dataDir, { recursive: true });
|
|
9372
|
-
const legacyOld =
|
|
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
|
|
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 =
|
|
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 =
|
|
9392
|
-
const envFiles = (await
|
|
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(
|
|
10242
|
+
legacyEnvs[name] = await readEncrypted(join16(envsDir, file));
|
|
9397
10243
|
}
|
|
9398
|
-
const servicesDir =
|
|
9399
|
-
const serviceFiles = (await
|
|
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(
|
|
10250
|
+
const data = await readEncrypted(join16(servicesDir, file));
|
|
9405
10251
|
if (data?.label) legacyServices[name] = data;
|
|
9406
10252
|
}
|
|
9407
|
-
const overridesDir =
|
|
9408
|
-
const overrideFiles = (await
|
|
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(
|
|
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 =
|
|
9422
|
-
const parserFiles = (await
|
|
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(
|
|
10271
|
+
const data = await readEncrypted(join16(parsersDir, file));
|
|
9426
10272
|
if (data?.id) legacyParsers.push(data);
|
|
9427
10273
|
}
|
|
9428
|
-
const projectMonoId =
|
|
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] =
|
|
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 =
|
|
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 =
|
|
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 ||
|
|
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 ||
|
|
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 ||
|
|
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:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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[
|
|
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 =
|
|
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
|
|
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(
|
|
9788
|
-
await atomicWrite(
|
|
9789
|
-
await atomicWrite(
|
|
9790
|
-
await atomicWrite(
|
|
9791
|
-
await atomicWrite(
|
|
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
|
|
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 =
|
|
10651
|
+
const keyJsonPath = join16(dataDir, dir, "dbs", "encryption-key.json");
|
|
9806
10652
|
try {
|
|
9807
|
-
const raw = await
|
|
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
|
-
|
|
9830
|
-
|
|
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
|
|
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 =
|
|
10692
|
+
const legacyKeyPath = join16(overridesDir, "..", "encryption-key.json");
|
|
9847
10693
|
try {
|
|
9848
|
-
await
|
|
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
|
|
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
|
|
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(
|
|
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[
|
|
10768
|
+
flatOverrides[randomUUID10()] = makeVariable(cleaned, value);
|
|
9923
10769
|
}
|
|
9924
10770
|
}
|
|
9925
10771
|
}
|
|
9926
10772
|
if (Object.keys(flatOverrides).length > 0) {
|
|
9927
|
-
await atomicWrite(
|
|
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(
|
|
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:
|
|
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 =
|
|
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(
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
11009
|
+
await stat5(join18(dataDir, ENCRYPTED_KEY_FILENAME2));
|
|
10164
11010
|
hasEncFile = true;
|
|
10165
11011
|
} catch {
|
|
10166
11012
|
}
|
|
10167
11013
|
try {
|
|
10168
|
-
await stat5(
|
|
11014
|
+
await stat5(join18(dataDir, VERIFY_FILENAME2));
|
|
10169
11015
|
hasVerify = true;
|
|
10170
11016
|
} catch {
|
|
10171
11017
|
}
|
|
10172
11018
|
try {
|
|
10173
|
-
await stat5(
|
|
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-
|
|
11946
|
+
//# sourceMappingURL=dist-2H7XZQCV.js.map
|