@runeya/runeya 2.0.151 → 2.0.152
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 +9 -7
- package/agent/index.js.map +1 -1
- package/agent-manager-SF4CAJJC.js +13 -0
- package/chat-Z7SFBEXS.js +28 -0
- package/{chunk-ANZF5NLB.js → chunk-26Q43CB2.js} +2 -2
- package/{chunk-IOPX7MWA.js → chunk-2DIW5EUT.js} +6 -6
- package/{chunk-ZRMLIQZL.js → chunk-3C3R5ZVX.js} +2 -2
- package/{chunk-I2EXJ2GI.js → chunk-3O54EIGT.js} +4 -4
- package/{chunk-KTRL6RDH.js → chunk-DTRZ5ZN7.js} +5 -5
- package/{chunk-3FSQ45SW.js → chunk-EUTW2S4P.js} +87 -16
- package/chunk-EUTW2S4P.js.map +1 -0
- package/{chunk-NVU7QNZO.js → chunk-MGKGIHT4.js} +2 -4
- package/{chunk-NVU7QNZO.js.map → chunk-MGKGIHT4.js.map} +1 -1
- package/{chunk-FYAKLTSY.js → chunk-PU6REPMY.js} +5 -5
- package/{chunk-FYAKLTSY.js.map → chunk-PU6REPMY.js.map} +1 -1
- package/{chunk-NKOG7375.js → chunk-QD2OVLK4.js} +2 -2
- package/{chunk-7O54TV2Y.js → chunk-QJSIN26R.js} +45 -38
- package/chunk-QJSIN26R.js.map +1 -0
- package/{chunk-3JIJSGQW.js → chunk-R4SKU6AO.js} +10 -12
- package/chunk-R4SKU6AO.js.map +1 -0
- package/{chunk-OCIKN4BK.js → chunk-RVZATF7R.js} +8 -8
- package/chunk-RVZATF7R.js.map +1 -0
- package/{chunk-YTO4O3UK.js → chunk-TBNCGMDM.js} +5 -5
- package/{companion-2YZZSKX5.js → companion-ZDEGNZ2G.js} +11 -9
- package/{desktop-installer-page-526XOYLO.js → desktop-installer-page-TBKSXVFD.js} +3 -3
- package/{desktop-launcher-WJPN6SPV.js → desktop-launcher-WBYP3KTF.js} +3 -3
- package/{environment-resolver-I6KF27QP.js → environment-resolver-R2GH6QOX.js} +6 -6
- package/{external-XHTD2IGX.js → external-UJ46WKBL.js} +3 -3
- package/index.js +15 -15
- package/index.js.map +1 -1
- package/{install-gate-VIJIEVF2.js → install-gate-TSCXUCYE.js} +2 -2
- package/mcp-server/external.js +7 -6
- package/mcp-server/external.js.map +1 -1
- package/mcp-server/index.js +7 -6
- package/mcp-server/index.js.map +1 -1
- package/package.json +1 -1
- package/{project-store-GONXVR2N.js → project-store-GBKM34QP.js} +7 -7
- package/settings-manager-VSOC3XGI.js +10 -0
- package/{src-QMPTXT7D.js → src-6WZKSNN7.js} +2 -2
- package/{src-ZDXFSJMJ.js → src-NWPED6TK.js} +33 -39
- package/src-NWPED6TK.js.map +1 -0
- package/{vault-resolver-MX7WFBJ5.js → vault-resolver-GGHULMG6.js} +3 -3
- package/web/assets/{AgentScalarChatInterface.vue-DXbf1TAm.js → AgentScalarChatInterface.vue-0pB6D9jL.js} +1 -1
- package/web/assets/{AgentTerminalPage-CgsW_pEb.js → AgentTerminalPage-D1c5A1Ol.js} +1 -1
- package/web/assets/{AiChatModals-DLTvqUBK.js → AiChatModals-B_Cn5wNS.js} +1 -1
- package/web/assets/AppLayout-BsFZ6nG3.js +1 -0
- package/web/assets/{AppLayout-BlhwflMq.css → AppLayout-Djk3-XbN.css} +1 -1
- package/web/assets/{AuditPage-HMNBcG9E.js → AuditPage-B_Ee8f0S.js} +1 -1
- package/web/assets/{CloudAccountPage-Ctf2gtpm.css → CloudAccountPage-CYcW_3tO.css} +1 -1
- package/web/assets/CloudAccountPage-D1L8Z_V3.js +1 -0
- package/web/assets/{CloudMessagingPage-Csbnn-Zm.js → CloudMessagingPage-CyZF0sdY.js} +1 -1
- package/web/assets/{DashboardPage-BeCmdPlp.js → DashboardPage-BeWd0GWF.js} +1 -1
- package/web/assets/{DirectoryPickerDialog-DpUZT_4M.js → DirectoryPickerDialog-BGhbB66i.js} +1 -1
- package/web/assets/{EnvironmentForm-ypeBVGl-.js → EnvironmentForm-B0FYydAK.js} +1 -1
- package/web/assets/EnvironmentForm-BnTLokUw.css +1 -0
- package/web/assets/{EnvironmentFormPage-B1gVyMWV.js → EnvironmentFormPage-BdTdOUBD.js} +1 -1
- package/web/assets/{EnvironmentListPage-CdE-ksue.js → EnvironmentListPage-Cy9Kg5pY.js} +1 -1
- package/web/assets/{KanbanBoardPage-_E6xalsS.js → KanbanBoardPage-BOEK_sKt.js} +3 -3
- package/web/assets/{KanbanListPage-DkjiefP8.js → KanbanListPage-BOxLKDpW.js} +1 -1
- package/web/assets/{LaunchProjectPage-C4kuqyMZ.js → LaunchProjectPage-zlY3PK3t.js} +1 -1
- package/web/assets/{LaunchServicesPage-s8HZF2aU.js → LaunchServicesPage-BsM0bP8N.js} +1 -1
- package/web/assets/{LaunchShell-GWtn69Hh.js → LaunchShell-BznQ7GLD.js} +1 -1
- package/web/assets/{LogViewer-B0XUfzs3.js → LogViewer-9e3e9X-R.js} +1 -1
- package/web/assets/{OrgKeyPrompt-Ch8jziJJ.js → OrgKeyPrompt-Ckq1sJ8-.js} +1 -1
- package/web/assets/{ProcessControls-bZbJJjkv.js → ProcessControls-Br_aLfW7.js} +1 -1
- package/web/assets/ProcessDetailPage-B7bD2jmj.css +1 -0
- package/web/assets/ProcessDetailPage-D3BNcm7N.js +1 -0
- package/web/assets/{ProjectCreateDialog-KjvGUZnr.js → ProjectCreateDialog-BzJRqXun.js} +1 -1
- package/web/assets/{ProjectLocationGate-DFe2hgxM.js → ProjectLocationGate-DjBwSAKc.js} +1 -1
- package/web/assets/{ProjectPublishDialog-BgPH5gGM.js → ProjectPublishDialog-yakoPg1D.js} +1 -1
- package/web/assets/{RandomJobTestPage-Bv3l2Trh.js → RandomJobTestPage-Dh0-shVq.js} +1 -1
- package/web/assets/{ScenarioEditorPage-BtKSjE2X.css → ScenarioEditorPage-BwLBCVeS.css} +1 -1
- package/web/assets/ScenarioEditorPage-DEdlwmf3.js +14 -0
- package/web/assets/{SendToChatDialog-CkWcPSvZ.js → SendToChatDialog-_ai4UP8l.js} +1 -1
- package/web/assets/{ServiceControlsBar-VywLkYdH.js → ServiceControlsBar-Bo-jHhJK.js} +1 -1
- package/web/assets/ServiceDetailPage-CWB3XIMA.js +2 -0
- package/web/assets/{ServiceDetailPage-CM5uzqpI.css → ServiceDetailPage-DuXvqXk4.css} +1 -1
- package/web/assets/{ServiceFormPage-v9c7DVdv.js → ServiceFormPage-BzARwyAF.js} +1 -1
- package/web/assets/{SettingsPage-CetQ42p1.js → SettingsPage-CsIXEQrW.js} +7 -7
- package/web/assets/{SettingsPage-B951NEBm.css → SettingsPage-NpFX7FAy.css} +1 -1
- package/web/assets/{UnlockPage-CZMvcAl1.js → UnlockPage-C08Itng5.js} +1 -1
- package/web/assets/{VaultMigrationPage-CeX0mpxh.js → VaultMigrationPage-BLZc5pXH.js} +1 -1
- package/web/assets/{VersionBadge-DDmZ4Dhc.js → VersionBadge-lBKbqt98.js} +1 -1
- package/web/assets/{WikiPage-3puq_dIL.js → WikiPage-RCU0T1IS.js} +1 -1
- package/web/assets/{WikiView-J4U7er--.js → WikiView-BV8MZ8qm.js} +1 -1
- package/web/assets/{add-scalar-classes-B7027Ds2.js → add-scalar-classes-CvUuKrM6.js} +1 -1
- package/web/assets/{agent.store-CAhnz7FI.js → agent.store-yrwtq9oP.js} +1 -1
- package/web/assets/{auth-DEwzvqNZ.js → auth-LbzJj0JF.js} +2 -2
- package/web/assets/{cloud-chat.store-Dw5IpDzc.js → cloud-chat.store-y_9urSQZ.js} +1 -1
- package/web/assets/{cloud-client-BmRvC15v.js → cloud-client-DhjJyZHU.js} +2 -2
- package/web/assets/{cloud-session-CxfJk0xr.js → cloud-session-BlihYcy2.js} +2 -2
- package/web/assets/dist-C9Qdh18x.js +1 -0
- package/web/assets/i18n-DxVwqoxL.js +1 -0
- package/web/assets/{index-DSYMPMcP.js → index-Ci2pSJuu.js} +7 -7
- package/web/assets/{index-CnXzQkLZ.css → index-Ck_3m_I9.css} +1 -1
- package/web/assets/{modal-CWJFP30c.js → modal-KpWdqKSY.js} +1 -1
- package/web/assets/notification.store-Bp37Xsss.js +1 -0
- package/web/assets/{project-launch.store-_peuBwFo.js → project-launch.store-AB257LRF.js} +1 -1
- package/web/assets/{project.store-eANeFYaH.js → project.store-qBZ_R1td.js} +1 -1
- package/web/assets/{scenario.store-C1Yj7c6l.js → scenario.store-B5ybSSzx.js} +1 -1
- package/web/assets/{service.store-Ck2_TmTB.js → service.store-BrKRjCtb.js} +1 -1
- package/web/assets/{settings-scope-GiU9Nkh7.js → settings-scope-BSdUKgjx.js} +1 -1
- package/web/assets/settings.store-C8guF9Fl.js +1 -0
- package/web/assets/{src-BsRUOIiC.js → src-Br-M9ln7.js} +1 -1
- package/web/assets/{src-Bfb0PBSC.css → src-dQGrnDw0.css} +1 -1
- package/web/assets/{src-Ip9ugq4e.js → src-recq0B6o.js} +2 -2
- package/web/assets/{trpc-Deb6EPHJ.js → trpc-CAKhySL_.js} +3 -3
- package/web/assets/{useCloudOrg-7IK98BjW.js → useCloudOrg-B_JPalTv.js} +1 -1
- package/web/assets/{useEnvVarAutocomplete-lfgStLwz.js → useEnvVarAutocomplete-DCZCMfLC.js} +1 -1
- package/web/assets/{useEnvVarAutocomplete-B-LJ9dBm.css → useEnvVarAutocomplete-gV2loQYv.css} +1 -1
- package/web/assets/{useProjectCatalog-J40rgwRP.js → useProjectCatalog-CCJAkQi_.js} +1 -1
- package/web/assets/{useProjectCloudOrg-zePV-ihH.js → useProjectCloudOrg-CG4CriU2.js} +1 -1
- package/web/assets/{useServiceUrlHost-BWtmwnoK.js → useServiceUrlHost-C1gL6Mjg.js} +1 -1
- package/web/index.html +17 -17
- package/agent-manager-2KTEZXRO.js +0 -13
- package/chat-N2AYGN7I.js +0 -28
- package/chunk-3FSQ45SW.js.map +0 -1
- package/chunk-3JIJSGQW.js.map +0 -1
- package/chunk-7O54TV2Y.js.map +0 -1
- package/chunk-DA2DZBTL.js +0 -43
- package/chunk-DA2DZBTL.js.map +0 -1
- package/chunk-OCIKN4BK.js.map +0 -1
- package/mcp-ready-TAPBAOZY.js +0 -9
- package/settings-manager-QOLMKVP2.js +0 -10
- package/src-ZDXFSJMJ.js.map +0 -1
- package/vault-resolver-MX7WFBJ5.js.map +0 -1
- package/web/assets/AppLayout-BFCPOgV1.js +0 -1
- package/web/assets/CloudAccountPage-DXMDa3Zw.js +0 -1
- package/web/assets/EnvironmentForm-Do3i-fOk.css +0 -1
- package/web/assets/ProcessDetailPage-CJcIxbLm.css +0 -1
- package/web/assets/ProcessDetailPage-Cy_9S9z8.js +0 -1
- package/web/assets/ScenarioEditorPage-DMZ_67kP.js +0 -14
- package/web/assets/ServiceDetailPage-C-RIYfjD.js +0 -2
- package/web/assets/dist-B6L3Jwj-.js +0 -1
- package/web/assets/i18n-BNITS3xi.js +0 -1
- package/web/assets/notification.store-C1JOcW8z.js +0 -1
- package/web/assets/settings.store-C6T1P_cU.js +0 -1
- /package/{agent-manager-2KTEZXRO.js.map → agent-manager-SF4CAJJC.js.map} +0 -0
- /package/{chat-N2AYGN7I.js.map → chat-Z7SFBEXS.js.map} +0 -0
- /package/{chunk-ANZF5NLB.js.map → chunk-26Q43CB2.js.map} +0 -0
- /package/{chunk-IOPX7MWA.js.map → chunk-2DIW5EUT.js.map} +0 -0
- /package/{chunk-ZRMLIQZL.js.map → chunk-3C3R5ZVX.js.map} +0 -0
- /package/{chunk-I2EXJ2GI.js.map → chunk-3O54EIGT.js.map} +0 -0
- /package/{chunk-KTRL6RDH.js.map → chunk-DTRZ5ZN7.js.map} +0 -0
- /package/{chunk-NKOG7375.js.map → chunk-QD2OVLK4.js.map} +0 -0
- /package/{chunk-YTO4O3UK.js.map → chunk-TBNCGMDM.js.map} +0 -0
- /package/{companion-2YZZSKX5.js.map → companion-ZDEGNZ2G.js.map} +0 -0
- /package/{desktop-installer-page-526XOYLO.js.map → desktop-installer-page-TBKSXVFD.js.map} +0 -0
- /package/{desktop-launcher-WJPN6SPV.js.map → desktop-launcher-WBYP3KTF.js.map} +0 -0
- /package/{environment-resolver-I6KF27QP.js.map → environment-resolver-R2GH6QOX.js.map} +0 -0
- /package/{external-XHTD2IGX.js.map → external-UJ46WKBL.js.map} +0 -0
- /package/{install-gate-VIJIEVF2.js.map → install-gate-TSCXUCYE.js.map} +0 -0
- /package/{mcp-ready-TAPBAOZY.js.map → project-store-GBKM34QP.js.map} +0 -0
- /package/{project-store-GONXVR2N.js.map → settings-manager-VSOC3XGI.js.map} +0 -0
- /package/{settings-manager-QOLMKVP2.js.map → src-6WZKSNN7.js.map} +0 -0
- /package/{src-QMPTXT7D.js.map → vault-resolver-GGHULMG6.js.map} +0 -0
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
serviceStore
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-DTRZ5ZN7.js";
|
|
4
4
|
import {
|
|
5
5
|
decryptValue,
|
|
6
6
|
deriveStableSecret,
|
|
7
7
|
getEncryptionKey,
|
|
8
8
|
isEncryptedValue,
|
|
9
9
|
stableEncryptValue
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-3O54EIGT.js";
|
|
11
11
|
import {
|
|
12
12
|
renameWithRetry
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-3C3R5ZVX.js";
|
|
14
14
|
import {
|
|
15
15
|
env,
|
|
16
16
|
keyedFilePath,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
listOrgVaults,
|
|
19
19
|
machineRoot,
|
|
20
20
|
orgVaultRoot
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-MGKGIHT4.js";
|
|
22
22
|
import {
|
|
23
23
|
AGENT_FETCH_TIMEOUT,
|
|
24
24
|
AGENT_HEALTH_POLL_INTERVAL,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
TraefikConfigSchema,
|
|
27
27
|
WS_RECONNECT_MAX,
|
|
28
28
|
WS_RECONNECT_MIN
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-RVZATF7R.js";
|
|
30
30
|
|
|
31
31
|
// ../server/src/services/agent-manager.ts
|
|
32
32
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
@@ -718,13 +718,11 @@ function buildAgentEnv() {
|
|
|
718
718
|
RUNEYA_AGENT_PORT: String(env.RUNEYA_AGENT_PORT),
|
|
719
719
|
RUNEYA_AGENT_HOST: env.RUNEYA_AGENT_HOST
|
|
720
720
|
};
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
return null;
|
|
725
|
-
}
|
|
726
|
-
agentEnv.PASSPHRASE_OVERRIDE = passphrase;
|
|
721
|
+
const passphrase = deriveStableSecret("local-agent-passphrase");
|
|
722
|
+
if (!passphrase) {
|
|
723
|
+
return null;
|
|
727
724
|
}
|
|
725
|
+
agentEnv.RUNEYA_AGENT_PASSPHRASE = passphrase;
|
|
728
726
|
const wanted = new Set(safeKeys.map((k) => k.toLowerCase()));
|
|
729
727
|
for (const [key, value] of Object.entries(process.env)) {
|
|
730
728
|
if (value && wanted.has(key.toLowerCase())) {
|
|
@@ -1666,4 +1664,4 @@ export {
|
|
|
1666
1664
|
traefikManager,
|
|
1667
1665
|
agentManager
|
|
1668
1666
|
};
|
|
1669
|
-
//# sourceMappingURL=chunk-
|
|
1667
|
+
//# sourceMappingURL=chunk-R4SKU6AO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../server/src/services/agent-manager.ts","../../server/src/services/agent-store.ts","../../server/src/services/agent-spawner.ts","../../server/src/services/instance-registry.ts","../../server/src/services/traefik-manager.ts","../../server/src/services/agent-ws-bridge.ts"],"sourcesContent":["import { EventEmitter } from 'node:events';\nimport WebSocket from 'ws';\nimport { z } from 'zod';\nimport { agentStore, type AgentOwner } from './agent-store.js';\nimport { serviceStore } from './service-store.js';\nimport { agentSpawner } from './agent-spawner.js';\nimport { traefikManager } from './traefik-manager.js';\nimport { AgentWSBridge } from './agent-ws-bridge.js';\nimport {\n WS_RECONNECT_MIN,\n WS_RECONNECT_MAX,\n AGENT_HEALTH_POLL_INTERVAL,\n AGENT_FETCH_TIMEOUT,\n TraefikConfigSchema,\n} from '@runeya/packages-shared';\nimport type { AgentConfig, AgentStatus } from '@runeya/packages-shared';\n\nconst HealthMessageSchema = z.object({\n status: z.string(),\n uptime: z.number(),\n runnersAvailable: z.array(z.enum(['native', 'docker'])),\n processCount: z.number(),\n runningCount: z.number(),\n lanIp: z.string().optional(),\n os: z.string().max(64).optional(),\n home: z.string().max(4096).optional(),\n cwd: z.string().max(4096).optional(),\n});\n\ninterface AgentConnection {\n config: AgentConfig;\n status: AgentStatus;\n healthWs?: WebSocket;\n bridge?: AgentWSBridge;\n reconnectTimer?: ReturnType<typeof setTimeout>;\n reconnectDelay: number;\n aliveTimer?: ReturnType<typeof setTimeout>;\n}\n\nconst MIN_RECONNECT_DELAY = WS_RECONNECT_MIN;\nconst MAX_RECONNECT_DELAY = WS_RECONNECT_MAX;\nconst ALIVE_TIMEOUT = AGENT_HEALTH_POLL_INTERVAL;\n\nfunction resolvePassthroughValues(names: string[]): Record<string, string> {\n const values: Record<string, string> = {};\n for (const name of names) {\n if (process.env[name] !== undefined) {\n values[name] = process.env[name]!;\n }\n }\n return values;\n}\n\nclass AgentManager extends EventEmitter {\n private connections: Map<string, AgentConnection> = new Map();\n\n /**\n * In-flight GET calls, keyed by agent + path + input.\n *\n * Several callers legitimately ask the same agent the same question at the\n * same moment — the sidebar and the git panel both read `git.status` for the\n * open service, the chat store, the chat page and the kanban board all read\n * `ai.findPtyBySession` for the same conversation. Sharing the pending\n * promise collapses those into a single round-trip: same response, same\n * instant, one less request queued on the agent. GET only — every tRPC query\n * is a read, whereas replaying a mutation must always hit the agent.\n */\n private inFlightGets: Map<string, Promise<unknown>> = new Map();\n\n constructor() {\n super();\n // Each WS client subscribes to up to 4 process event types + 1 agent status,\n // plus 1 bridge fan-out per agent. Two browser tabs = 10+ listeners easily.\n this.setMaxListeners(50);\n }\n\n // Make an authenticated fetch call to an agent's tRPC endpoint.\n // GET calls that are already in flight with identical input are shared rather\n // than re-issued (see `inFlightGets`).\n private async agentCall<T>(agent: AgentConfig, path: string, input?: unknown, method: 'GET' | 'POST' = 'GET', timeout = AGENT_FETCH_TIMEOUT): Promise<T> {\n if (method !== 'GET') return this.performAgentCall<T>(agent, path, input, method, timeout);\n\n let serialisedInput: string;\n try {\n serialisedInput = input === undefined ? '' : JSON.stringify(input);\n } catch {\n // Non-serialisable input — can't key it, so just issue the call.\n return this.performAgentCall<T>(agent, path, input, method, timeout);\n }\n // The timeout is part of the key: a caller must never inherit a shorter\n // deadline than it asked for by piggybacking on someone else's call.\n const key = `${agent.id}\\u0000${path}\\u0000${timeout}\\u0000${serialisedInput}`;\n\n const pending = this.inFlightGets.get(key);\n if (pending) return pending as Promise<T>;\n\n const call = this.performAgentCall<T>(agent, path, input, method, timeout)\n .finally(() => { this.inFlightGets.delete(key); });\n this.inFlightGets.set(key, call);\n return call;\n }\n\n private async performAgentCall<T>(agent: AgentConfig, path: string, input?: unknown, method: 'GET' | 'POST' = 'GET', timeout = AGENT_FETCH_TIMEOUT): Promise<T> {\n const url = new URL(`/api/trpc/${path}`, agent.url);\n const headers: Record<string, string> = {\n 'Authorization': `Bearer ${agent.passphrase}`,\n 'Content-Type': 'application/json',\n };\n\n // console.log(`[agent-manager] Calling agent ${agent.id} → ${method} ${path}`);\n\n let response: Response;\n try {\n if (method === 'GET') {\n if (input !== undefined) {\n url.searchParams.set('input', JSON.stringify(input));\n }\n response = await fetch(url.toString(), { headers, signal: AbortSignal.timeout(timeout) });\n } else {\n response = await fetch(url.toString(), {\n method: 'POST',\n headers,\n body: JSON.stringify(input !== undefined ? input : {}),\n signal: AbortSignal.timeout(timeout),\n });\n }\n } catch (err: unknown) {\n console.error('[agent-manager] Agent call failed (' + method + ' ' + path + '):', err instanceof Error ? err.message : String(err)); // nosemgrep: unsafe-formatstring\n throw err;\n }\n\n if (!response.ok) {\n let agentMessage = '';\n try {\n const errBody = await response.json() as { error?: { message?: string }; message?: string };\n agentMessage = errBody?.error?.message ?? errBody?.message ?? '';\n } catch { /* ignore parse errors */ }\n const detail = agentMessage ? `: ${agentMessage}` : '';\n console.error('[agent-manager] Agent call failed: HTTP', response.status, 'for', method, path, detail);\n throw new Error(`Agent call failed: HTTP ${response.status}${detail}`);\n }\n\n const json = await response.json() as { error?: { message?: string }; result?: { data?: T } };\n if (json.error) {\n console.error('[agent-manager] Agent error on', path + ':', json.error);\n throw new Error(`Agent error on ${path}: ${json.error.message ?? 'unknown'}`);\n }\n return json.result?.data as T;\n }\n\n // Query wrapper\n async agentQuery<T>(agentId: string, path: string, input?: unknown): Promise<T> {\n const agent = this.getAgent(agentId);\n if (!agent) throw new Error(`Agent ${agentId} not found`);\n return this.agentCall<T>(agent.config, path, input, 'GET');\n }\n\n // Mutation wrapper\n async agentMutation<T>(agentId: string, path: string, input?: unknown, timeout?: number): Promise<T> {\n const agent = this.getAgent(agentId);\n if (!agent) throw new Error(`Agent ${agentId} not found`);\n return this.agentCall<T>(agent.config, path, input, 'POST', timeout);\n }\n\n /**\n * Open a dynamic tRPC subscription on the agent's WS bridge.\n * Returns an unsubscribe function.\n * Used for per-session streaming (e.g. terminal.onData).\n */\n agentSubscribe(\n agentId: string,\n path: string,\n input: unknown,\n onData: (data: unknown) => void,\n onStop?: () => void,\n ): () => void {\n const conn = this.connections.get(agentId);\n if (!conn?.bridge) throw new Error(`Agent ${agentId} not connected via WebSocket`);\n return conn.bridge.subscribeWithInput(path, input, onData, onStop);\n }\n\n async init(): Promise<void> {\n const agents = await agentStore.list();\n for (const agent of agents) {\n this.connectAgent(agent);\n }\n }\n\n /**\n * `owner` dit à quelle organisation l'agent appartient, ou `null` pour la\n * machine — l'agent local, qui n'exécute des processus que sur ce poste.\n */\n async register(\n name: string,\n url: string,\n passphrase: string,\n owner: AgentOwner = null,\n ): Promise<AgentConfig> {\n const config = await agentStore.register(name, url, passphrase, owner);\n const isReRegistration = this.connections.has(config.id);\n\n // If already connected (e.g. loaded by init() then re-registered by spawner),\n // close the stale WS before opening a fresh one with the new passphrase.\n if (isReRegistration) {\n console.log(`[agent-manager] Re-registering agent ${config.name} (${config.id})`);\n this.disconnectAgent(config.id);\n // Small delay to ensure WS are fully closed before reconnecting\n await new Promise(resolve => setTimeout(resolve, 200));\n }\n\n this.connectAgent(config);\n console.log(`[agent-manager] Agent ${config.name} (${config.id}) connection established`);\n return config;\n }\n\n /**\n * Register a local agent in-memory only (no persistence to agentStore).\n * The local agent is always auto-spawned with deterministic config,\n * so it doesn't need disk persistence.\n * Also reassigns orphaned services (from old UUID-based agent IDs) to this agent.\n */\n async registerLocal(config: AgentConfig): Promise<void> {\n const isReRegistration = this.connections.has(config.id);\n\n if (isReRegistration) {\n console.log(`[agent-manager] Re-registering local agent ${config.name} (${config.id})`);\n this.disconnectAgent(config.id);\n await new Promise(resolve => setTimeout(resolve, 200));\n }\n\n // Load persisted passthrough env for the local agent\n const storedPassthrough = agentStore.getPassthroughEnv(config.id);\n const configWithPassthrough = { ...config, passthroughEnv: storedPassthrough };\n\n // Reassign orphaned services: any service whose agentId doesn't match\n // a known agent gets reassigned to this local agent (migration from UUID → stable ID).\n await this.reassignOrphanedServices(config.id);\n\n this.connectAgent(configWithPassthrough);\n console.log(`[agent-manager] Local agent ${config.name} (${config.id}) connected (in-memory only)`);\n }\n\n /**\n * Find services assigned to unknown agent IDs and reassign them to the given agent.\n * This handles the migration from random UUID-based agent IDs to the stable 'local-agent' ID.\n */\n private async reassignOrphanedServices(localAgentId: string): Promise<void> {\n const allServices = await serviceStore.list();\n const persistedAgents = await agentStore.list();\n const knownIds = new Set([localAgentId, ...persistedAgents.map(a => a.id)]);\n\n for (const service of allServices) {\n if (service.agentId && !knownIds.has(service.agentId)) {\n console.log(`[agent-manager] Reassigning orphaned service ${service.name} (${service.id}) from ${service.agentId} → ${localAgentId}`);\n await serviceStore.update({ id: service.id, agentId: localAgentId });\n }\n }\n }\n\n async update(agentId: string, fields: { name?: string; url?: string; passphrase?: string; passthroughEnv?: string[] }): Promise<AgentConfig> {\n const isLocal = agentSpawner.getLocalAgentId() === agentId;\n\n // Local agent: only passthroughEnv is allowed\n if (isLocal) {\n const hasOtherFields = fields.name !== undefined || fields.url !== undefined || fields.passphrase !== undefined;\n if (hasOtherFields) {\n throw new Error('Cannot modify the local agent');\n }\n }\n\n // Handle passthroughEnv separately (persisted in agentStore, pushed to agent)\n if (fields.passthroughEnv !== undefined) {\n await agentStore.savePassthroughEnv(agentId, fields.passthroughEnv);\n\n const conn = this.connections.get(agentId);\n if (conn) {\n conn.config = { ...conn.config, passthroughEnv: fields.passthroughEnv };\n // Push to the agent\n try {\n await this.agentMutation(agentId, 'config.setPassthroughEnv', {\n names: fields.passthroughEnv,\n values: resolvePassthroughValues(fields.passthroughEnv),\n });\n } catch (err) {\n console.error('[agent-manager] Failed to push passthroughEnv to agent', agentId + ':', err instanceof Error ? err.message : String(err));\n }\n }\n }\n\n // For local agent with only passthroughEnv, return config from connection\n if (isLocal) {\n const conn = this.connections.get(agentId);\n if (!conn) throw new Error(`Agent ${agentId} not found`);\n return conn.config;\n }\n\n // Remote agent: update name/url/passphrase in the store\n const { passthroughEnv: _pt, ...storeFields } = fields;\n const hasStoreFields = storeFields.name !== undefined || storeFields.url !== undefined || storeFields.passphrase !== undefined;\n\n if (hasStoreFields) {\n const updated = await agentStore.update(agentId, storeFields);\n if (!updated) {\n throw new Error(`Agent ${agentId} not found`);\n }\n const conn = this.connections.get(agentId);\n if (conn) {\n conn.config = updated;\n if (storeFields.url !== undefined || storeFields.passphrase !== undefined) {\n this.disconnectAgent(agentId);\n this.connectAgent(updated);\n }\n }\n return updated;\n }\n\n // passthroughEnv-only update for remote agent\n const conn = this.connections.get(agentId);\n if (!conn) throw new Error(`Agent ${agentId} not found`);\n return conn.config;\n }\n\n async remove(agentId: string): Promise<boolean> {\n // Prevent removal of the local spawned agent\n if (agentSpawner.getLocalAgentId() === agentId) {\n throw new Error('Cannot remove the local agent');\n }\n this.disconnectAgent(agentId);\n return agentStore.remove(agentId);\n }\n\n private connectAgent(config: AgentConfig): void {\n const connection: AgentConnection = {\n config,\n status: {\n connected: false,\n lastSeen: null,\n uptime: 0,\n runnersAvailable: [],\n },\n reconnectDelay: MIN_RECONNECT_DELAY,\n };\n\n this.connections.set(config.id, connection);\n this.openHealthWs(config.id);\n this.openBridge(config.id);\n }\n\n private openBridge(agentId: string): void {\n const conn = this.connections.get(agentId);\n if (!conn) return;\n\n console.log(`[agent-manager] Opening tRPC bridge for agent ${conn.config.name} (${agentId})`);\n\n const bridge = new AgentWSBridge(agentId, conn.config.url, conn.config.passphrase);\n conn.bridge = bridge;\n\n // Fan-out bridge events to the AgentManager\n const eventTypes = ['process:status', 'process:log', 'process:health', 'process:metrics'] as const;\n for (const eventType of eventTypes) {\n bridge.on(eventType, (data: unknown) => {\n this.emit(eventType, data);\n });\n }\n\n bridge.connect();\n }\n\n private disconnectAgent(agentId: string): void {\n const conn = this.connections.get(agentId);\n if (!conn) return;\n\n console.log(`[agent-manager] Disconnecting agent ${conn.config.name} (${agentId})`);\n\n if (conn.healthWs) {\n conn.healthWs.removeAllListeners();\n conn.healthWs.close();\n conn.healthWs = undefined;\n }\n if (conn.bridge) {\n conn.bridge.disconnect();\n conn.bridge = undefined;\n }\n if (conn.reconnectTimer) {\n clearTimeout(conn.reconnectTimer);\n conn.reconnectTimer = undefined;\n }\n if (conn.aliveTimer) {\n clearTimeout(conn.aliveTimer);\n conn.aliveTimer = undefined;\n }\n\n this.connections.delete(agentId);\n this.emit('status', { agentId, status: { connected: false, lastSeen: null, uptime: 0, runnersAvailable: [] } satisfies AgentStatus });\n }\n\n private openHealthWs(agentId: string): void {\n const conn = this.connections.get(agentId);\n if (!conn) return;\n\n // Build WS URL from agent HTTP URL\n const httpUrl = new URL(conn.config.url);\n const wsProtocol = httpUrl.protocol === 'https:' ? 'wss:' : 'ws:';\n const wsUrl = `${wsProtocol}//${httpUrl.host}/ws/health`;\n\n const ws = new WebSocket(wsUrl, {\n headers: { 'Authorization': `Bearer ${conn.config.passphrase}` },\n });\n conn.healthWs = ws;\n\n const resetAliveTimer = () => {\n if (conn.aliveTimer) clearTimeout(conn.aliveTimer);\n conn.aliveTimer = setTimeout(() => {\n console.debug(`[agent-manager] Agent ${conn.config.name} (${agentId}) alive timeout — closing WS`);\n ws.close();\n }, ALIVE_TIMEOUT);\n };\n\n ws.on('open', () => {\n console.debug(`[agent-manager] WS open to agent ${conn.config.name} (${agentId})`);\n resetAliveTimer();\n });\n\n ws.on('message', (raw: Buffer | string) => {\n try {\n const health = HealthMessageSchema.parse(\n JSON.parse(typeof raw === 'string' ? raw : raw.toString()),\n );\n\n // Only reset alive timer after successful validation\n resetAliveTimer();\n\n const wasConnected = conn.status.connected;\n conn.status = {\n connected: true,\n lastSeen: new Date().toISOString(),\n uptime: health.uptime,\n runnersAvailable: health.runnersAvailable,\n lanIp: health.lanIp,\n os: health.os,\n home: health.home,\n cwd: health.cwd,\n };\n\n // Reset reconnect delay on successful message\n conn.reconnectDelay = MIN_RECONNECT_DELAY;\n\n this.emit('status', { agentId, status: { ...conn.status } satisfies AgentStatus });\n\n if (!wasConnected) {\n console.debug(`[agent-manager] Agent ${conn.config.name} (${agentId}) connected`);\n void this.pushPassthroughEnv(agentId);\n void this.reconcileAiSpawns(agentId);\n const rawTraefikConfig = agentStore.getTraefikConfig(agentId);\n if (rawTraefikConfig) {\n const parsed = TraefikConfigSchema.safeParse(rawTraefikConfig);\n if (parsed.success) {\n // Decrypt DNS credentials before pushing to agent — the stored config\n // holds encrypted ciphertext which is not valid as env var values.\n const configToPush = {\n ...parsed.data,\n dnsCredentials: traefikManager.decryptDnsCredentials(parsed.data.dnsCredentials ?? {}),\n };\n void this.pushTraefikConfig(agentId, configToPush);\n } else {\n console.error('[agent-manager] Invalid Traefik config in store for agent', agentId);\n }\n }\n }\n } catch (err) {\n console.debug('[agent-manager] Failed to parse health message for agent', agentId + ':', err);\n }\n });\n\n ws.on('close', () => {\n if (conn.aliveTimer) {\n clearTimeout(conn.aliveTimer);\n conn.aliveTimer = undefined;\n }\n conn.healthWs = undefined;\n\n if (conn.status.connected) {\n console.debug(`[agent-manager] Agent ${conn.config.name} (${agentId}) disconnected`);\n conn.status.connected = false;\n this.emit('status', { agentId, status: { ...conn.status } satisfies AgentStatus });\n }\n\n // Schedule reconnect only if still tracked\n if (this.connections.has(agentId)) {\n this.scheduleReconnect(agentId);\n }\n });\n\n ws.on('error', (err) => {\n // Error is always followed by close event, no need to handle reconnect here\n if (conn.status.connected) {\n console.debug('[agent-manager] WS error for agent', conn.config.name, '(' + agentId + '):', err.message);\n }\n });\n }\n\n private scheduleReconnect(agentId: string): void {\n const conn = this.connections.get(agentId);\n if (!conn) return;\n\n // Clear any existing reconnect timer\n if (conn.reconnectTimer) {\n clearTimeout(conn.reconnectTimer);\n }\n\n conn.reconnectTimer = setTimeout(() => {\n conn.reconnectTimer = undefined;\n if (this.connections.has(agentId)) {\n this.openHealthWs(agentId);\n }\n }, conn.reconnectDelay);\n\n // Exponential backoff: 1s → 2s → 4s → 8s → 15s (capped)\n conn.reconnectDelay = Math.min(conn.reconnectDelay * 2, MAX_RECONNECT_DELAY);\n }\n\n /**\n * On agent (re)connect, discover any AI spawns still alive on the agent.\n * Log them for now — full rehydration of ActiveSessionRegistry is a follow-up.\n */\n private async reconcileAiSpawns(agentId: string): Promise<void> {\n try {\n const spawns = await this.agentQuery<Array<{ spawnId: string; cli: string; status: string; bufferLength: number }>>(agentId, 'ai.list');\n if (spawns.length > 0) {\n const summary = spawns.map(s => `${s.spawnId} (${s.cli}, ${s.status}, buf:${s.bufferLength})`).join(', ');\n console.log('[agent-manager] Agent %s has %d AI spawn(s): %s', agentId, spawns.length, summary);\n // TODO: rehydrate ActiveSessionRegistry for running spawns that match known conversations\n // For now, ack done spawns without a matching session to allow cleanup\n for (const spawn of spawns) {\n if (spawn.status !== 'running') {\n this.agentMutation(agentId, 'ai.ack', { spawnId: spawn.spawnId }).catch(() => {});\n }\n }\n }\n } catch (err) {\n console.debug('[agent-manager] Failed to reconcile AI spawns for agent', agentId + ':', err instanceof Error ? err.message : String(err));\n }\n }\n\n // Push passthrough env variables to agent on (re)connection\n private async pushPassthroughEnv(agentId: string): Promise<void> {\n const conn = this.connections.get(agentId);\n if (conn && conn.config.passthroughEnv && conn.config.passthroughEnv.length > 0) {\n try {\n await this.agentMutation(agentId, 'config.setPassthroughEnv', {\n names: conn.config.passthroughEnv,\n values: resolvePassthroughValues(conn.config.passthroughEnv),\n });\n console.log('[agent-manager] Pushed passthroughEnv to agent', agentId + ':', conn.config.passthroughEnv.join(', '));\n } catch (err) {\n console.error('[agent-manager] Failed to push passthroughEnv to agent', agentId + ':', err instanceof Error ? err.message : String(err));\n }\n }\n }\n\n // Push Traefik config to agent on (re)connection or after update\n async pushTraefikConfig(agentId: string, config: unknown): Promise<void> {\n const conn = this.connections.get(agentId);\n if (!conn) return;\n try {\n await this.agentMutation(agentId, 'config.setTraefikConfig', config);\n } catch (err) {\n console.error('[agent-manager] Failed to push Traefik config to agent', agentId + ':', err instanceof Error ? err.message : String(err));\n }\n }\n\n /** Returns the local agent ID, or first connected agent, or null. */\n getDefaultAgentId(): string | null {\n const localId = agentSpawner.getLocalAgentId();\n if (localId && this.connections.get(localId)?.status.connected) return localId;\n for (const [id, conn] of this.connections) {\n if (conn.status.connected) return id;\n }\n return null;\n }\n\n getAgent(agentId: string): AgentConnection | undefined {\n return this.connections.get(agentId);\n }\n\n getAgentConfig(agentId: string): AgentConfig | undefined {\n return this.connections.get(agentId)?.config;\n }\n\n listAgents(): Array<{ config: AgentConfig; status: AgentStatus; isLocal: boolean }> {\n const localId = agentSpawner.getLocalAgentId();\n return Array.from(this.connections.values()).map(conn => ({\n config: { ...conn.config, passphrase: '***' }, // Don't expose passphrase\n status: conn.status,\n isLocal: conn.config.id === localId,\n }));\n }\n\n getStatus(agentId: string): AgentStatus | null {\n return this.connections.get(agentId)?.status ?? null;\n }\n\n async shutdown(): Promise<void> {\n const ids = [...this.connections.keys()];\n for (const id of ids) {\n this.disconnectAgent(id);\n }\n }\n}\n\nexport const agentManager = new AgentManager();\n","import { randomUUID, randomBytes, createCipheriv, createDecipheriv } from 'node:crypto';\nimport { readFile, writeFile, mkdir, chmod } from 'node:fs/promises';\nimport { renameWithRetry } from './run-store-utils.js';\nimport { dirname, join } from 'node:path';\nimport { keyedFilePath, legacyMachineFilePath, orgVaultRoot, listOrgVaults } from './vault-resolver.js';\nimport { getEncryptionKey } from '../utils/encryption-key.js';\nimport type { AgentConfig } from '@runeya/packages-shared';\n\nconst FILENAME = 'agents.json';\nconst ALGORITHM = 'aes-256-gcm';\n\ninterface StoredAgent {\n id: string;\n name: string;\n url: string;\n encrypted: string; // hex\n iv: string; // hex\n authTag: string; // hex\n}\n\nfunction encrypt(plaintext: string, vault?: string): { encrypted: string; iv: string; authTag: string } {\n const maybeKey = getEncryptionKey(vault);\n if (!maybeKey) {\n throw new Error('Encryption key not loaded — please provide the key via setup UI');\n }\n const key: Buffer = maybeKey; // Type narrowing\n const iv = randomBytes(16);\n const cipher = createCipheriv(ALGORITHM, key, iv);\n let enc = cipher.update(plaintext, 'utf-8', 'hex');\n enc += cipher.final('hex');\n const authTag = cipher.getAuthTag().toString('hex');\n return { encrypted: enc, iv: iv.toString('hex'), authTag };\n}\n\nfunction decrypt(encrypted: string, ivHex: string, authTagHex: string, vault?: string): string {\n // Validate all hex inputs before Buffer.from()\n if (!/^[0-9a-f]+$/i.test(encrypted) || !/^[0-9a-f]+$/i.test(ivHex) || !/^[0-9a-f]+$/i.test(authTagHex)) {\n throw new Error('Invalid hex string in encrypted agent data');\n }\n const maybeKey = getEncryptionKey(vault);\n if (!maybeKey) {\n throw new Error('Encryption key not loaded — please provide the key via setup UI');\n }\n const key: Buffer = maybeKey; // Type narrowing\n const iv = Buffer.from(ivHex, 'hex');\n const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: 16 });\n decipher.setAuthTag(Buffer.from(authTagHex, 'hex'));\n let dec = decipher.update(encrypted, 'hex', 'utf-8');\n dec += decipher.final('utf-8');\n return dec;\n}\n\ninterface AgentsFileData {\n agents: StoredAgent[];\n passthroughEnv: Record<string, string[]>;\n traefikConfigs: Record<string, unknown>;\n}\n\n/**\n * Où vit un agent : dans le coffre d'une organisation, ou sur la machine.\n *\n * Un agent décrivait jusqu'ici la machine, pour une seule raison — la clé qui\n * le chiffre y est rangée. Mais un service porte `agentId`, et ce champ part\n * dans le document synchronisé : la référence circule à l'échelle de\n * l'organisation pendant que sa cible reste enfermée ici. C'est ce décalage que\n * la portée corrige. Un agent d'organisation est rangé sous\n * `~/.runeya/orgs/<org>/`, et disparaît avec elle.\n *\n * `null` désigne la machine — le cas de l'agent local, qui n'est effectivement\n * l'affaire de personne d'autre : il exécute des processus sur CE poste.\n */\nexport type AgentOwner = string | null;\n\nclass AgentStore {\n private agents: Map<string, StoredAgent> = new Map();\n private passthroughEnv: Record<string, string[]> = {};\n private traefikConfigs: Record<string, unknown> = {};\n /** Le propriétaire de chaque agent, d'après le coffre où il a été lu. */\n private owners: Map<string, AgentOwner> = new Map();\n /**\n * Tous les coffres rencontrés, y compris ceux devenus vides.\n *\n * Déduire cette liste des agents encore présents laisserait le dernier agent\n * d'une organisation dans son fichier après suppression : plus personne pour\n * désigner ce coffre, donc plus personne pour le réécrire.\n */\n private knownOwners: Set<AgentOwner> = new Set([null]);\n private loaded = false;\n\n /**\n * Le fichier d'un propriétaire donné.\n *\n * Pour la machine, il reste à côté de la clé qui l'ouvre (`keyedFilePath`) :\n * lu sous une autre clé il se déchiffrerait en blancs, puis serait réécrit\n * blanc. Pour une organisation, c'est son coffre — la clé, elle, répond\n * toujours pour toute la machine.\n */\n /** L'identifiant de clé d'un agent : son organisation, ou la machine. */\n private vaultOf(id: string): string | undefined {\n return this.owners.get(id) ?? undefined;\n }\n\n private filePathFor(owner: AgentOwner): Promise<string> {\n if (owner === null) return keyedFilePath(FILENAME);\n return Promise.resolve(join(orgVaultRoot(owner), FILENAME));\n }\n\n /** Where older versions kept it — read only, so an older Runeya keeps working. */\n private getLegacyFilePath(): string {\n return legacyMachineFilePath(FILENAME);\n }\n\n /** Ce qu'un fichier de coffre contient, ou null s'il n'y en a pas. */\n private async readOwnerFile(owner: AgentOwner): Promise<AgentsFileData | null> {\n const paths = owner === null\n ? [await this.filePathFor(null), this.getLegacyFilePath()]\n : [await this.filePathFor(owner)];\n for (const path of paths) {\n let raw: string;\n try {\n raw = await readFile(path, 'utf-8');\n } catch {\n continue;\n }\n try {\n const data: unknown = JSON.parse(raw);\n // Legacy format: plain StoredAgent[]\n if (Array.isArray(data)) {\n return { agents: data as StoredAgent[], passthroughEnv: {}, traefikConfigs: {} };\n }\n const fileData = data as AgentsFileData;\n return {\n agents: fileData.agents ?? [],\n passthroughEnv: fileData.passthroughEnv ?? {},\n traefikConfigs: fileData.traefikConfigs ?? {},\n };\n } catch {\n // Fichier illisible : le suivant, plutôt que de perdre tout le reste.\n }\n }\n return null;\n }\n\n /**\n * Lire la machine puis chaque organisation présente sur ce disque.\n *\n * Les organisations sont relues du disque et non d'une liste tenue à part :\n * un coffre qui existe est une organisation dont les agents comptent, et\n * c'est déjà comme ça que la synchronisation décide de sa portée.\n */\n private async load(): Promise<void> {\n if (this.loaded) return;\n const owners: AgentOwner[] = [null, ...await listOrgVaults()];\n for (const owner of owners) {\n const data = await this.readOwnerFile(owner);\n if (!data) continue;\n this.knownOwners.add(owner);\n for (const a of data.agents) {\n this.agents.set(a.id, a);\n this.owners.set(a.id, owner);\n }\n Object.assign(this.passthroughEnv, data.passthroughEnv);\n Object.assign(this.traefikConfigs, data.traefikConfigs);\n }\n this.loaded = true;\n }\n\n /**\n * Réécrire chaque coffre concerné.\n *\n * Un agent emporte son `passthroughEnv` et sa configuration Traefik dans le\n * fichier de son propriétaire : les laisser côté machine rendrait la portée\n * mensongère — l'organisation partirait en laissant ses miettes derrière.\n * Tous les coffres sont réécrits, y compris ceux devenus vides, sinon un\n * agent déplacé subsisterait dans les deux.\n */\n private async save(): Promise<void> {\n const byOwner = new Map<AgentOwner, StoredAgent[]>();\n // Un coffre vidé doit être réécrit vide, donc on part des propriétaires\n // connus et pas seulement de ceux qui ont encore un agent.\n for (const owner of this.knownOwners) byOwner.set(owner, []);\n for (const agent of this.agents.values()) {\n const owner = this.owners.get(agent.id) ?? null;\n byOwner.set(owner, [...(byOwner.get(owner) ?? []), agent]);\n }\n\n for (const [owner, agents] of byOwner) {\n const ids = new Set(agents.map((a) => a.id));\n const fileData: AgentsFileData = {\n agents,\n passthroughEnv: Object.fromEntries(\n Object.entries(this.passthroughEnv).filter(([id]) => ids.has(id)),\n ),\n traefikConfigs: Object.fromEntries(\n Object.entries(this.traefikConfigs).filter(([id]) => ids.has(id)),\n ),\n };\n const filePath = await this.filePathFor(owner);\n await mkdir(dirname(filePath), { recursive: true });\n const tmpPath = filePath + '.tmp';\n await writeFile(tmpPath, JSON.stringify(fileData, null, 2), 'utf-8');\n await renameWithRetry(tmpPath, filePath);\n await chmod(filePath, 0o600);\n }\n }\n\n private toAgentConfig(stored: StoredAgent): AgentConfig {\n return {\n id: stored.id,\n name: stored.name,\n url: stored.url,\n // La passphrase est chiffrée par la clé du coffre où l'agent est rangé :\n // celle de son organisation, ou celle de la machine.\n passphrase: decrypt(stored.encrypted, stored.iv, stored.authTag, this.vaultOf(stored.id)),\n passthroughEnv: this.passthroughEnv[stored.id] ?? [],\n };\n }\n\n async list(): Promise<AgentConfig[]> {\n await this.load();\n // If no encryption key, return empty list (setup mode)\n if (!getEncryptionKey()) {\n return [];\n }\n return Array.from(this.agents.values()).map(a => this.toAgentConfig(a));\n }\n\n async get(id: string): Promise<AgentConfig | null> {\n await this.load();\n // If no encryption key, return null (setup mode)\n if (!getEncryptionKey()) {\n return null;\n }\n const stored = this.agents.get(id);\n if (!stored) return null;\n return this.toAgentConfig(stored);\n }\n\n async register(name: string, url: string, passphrase: string, owner: AgentOwner = null): Promise<AgentConfig> {\n await this.load();\n // Check if an agent with the same URL already exists\n const existing = Array.from(this.agents.values()).find(a => a.url === url);\n // Un ré-enregistrement garde le coffre où l'agent vit déjà : chiffrer sous\n // une autre clé que celle de son fichier le rendrait illisible sur place.\n const vault = existing ? this.vaultOf(existing.id) : (owner ?? undefined);\n const { encrypted, iv, authTag } = encrypt(passphrase, vault);\n if (existing) {\n // Update existing agent instead of creating duplicate\n existing.name = name;\n existing.encrypted = encrypted;\n existing.iv = iv;\n existing.authTag = authTag;\n await this.save();\n return { id: existing.id, name, url, passphrase, passthroughEnv: this.passthroughEnv[existing.id] ?? [] };\n }\n const id = randomUUID();\n const stored: StoredAgent = { id, name, url, encrypted, iv, authTag };\n this.agents.set(id, stored);\n this.owners.set(id, owner);\n this.knownOwners.add(owner);\n await this.save();\n return { id, name, url, passphrase, passthroughEnv: [] };\n }\n\n async remove(id: string): Promise<boolean> {\n await this.load();\n if (!this.agents.has(id)) return false;\n this.agents.delete(id);\n this.owners.delete(id);\n delete this.passthroughEnv[id];\n delete this.traefikConfigs[id];\n await this.save();\n return true;\n }\n\n getPassthroughEnv(agentId: string): string[] {\n return this.passthroughEnv[agentId] ?? [];\n }\n\n async savePassthroughEnv(agentId: string, names: string[]): Promise<void> {\n await this.load();\n this.passthroughEnv[agentId] = names;\n await this.save();\n }\n\n getTraefikConfig(agentId: string): unknown {\n return this.traefikConfigs[agentId] ?? null;\n }\n\n async saveTraefikConfig(agentId: string, config: unknown): Promise<void> {\n await this.load();\n this.traefikConfigs[agentId] = config;\n await this.save();\n }\n\n async update(id: string, fields: { name?: string; url?: string; passphrase?: string }): Promise<AgentConfig | null> {\n await this.load();\n const stored = this.agents.get(id);\n if (!stored) return null;\n if (fields.name !== undefined) stored.name = fields.name;\n if (fields.url !== undefined) stored.url = fields.url;\n if (fields.passphrase !== undefined) {\n const { encrypted, iv, authTag } = encrypt(fields.passphrase, this.vaultOf(id));\n stored.encrypted = encrypted;\n stored.iv = iv;\n stored.authTag = authTag;\n }\n await this.save();\n return this.toAgentConfig(stored);\n }\n\n async updateUrl(id: string, url: string): Promise<AgentConfig | null> {\n await this.load();\n const stored = this.agents.get(id);\n if (!stored) return null;\n stored.url = url;\n this.agents.set(id, stored);\n await this.save();\n return this.toAgentConfig(stored);\n }\n}\n\nexport const agentStore = new AgentStore();\n","import { spawn, type ChildProcess } from 'node:child_process';\nimport { createInterface } from 'node:readline';\nimport { watch } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { env } from '../config/env.js';\nimport { PASSPHRASE_TIMEOUT } from '@runeya/packages-shared';\nimport { agentManager } from './agent-manager.js';\nimport { setInstanceAgentPid } from './instance-registry.js';\nimport { deriveStableSecret } from '../utils/encryption-key.js';\n\n/**\n * Environment variables inherited by the spawned agent, per platform.\n * Only pass system-essential and explicitly needed variables —\n * never spread all of process.env to avoid leaking secrets.\n */\nconst POSIX_SAFE_KEYS = [\n 'PATH', 'HOME', 'USER', 'SHELL', 'LANG', 'TERM', 'NODE_ENV', 'TMPDIR', 'TZ',\n 'DBUS_SESSION_BUS_ADDRESS', 'XDG_RUNTIME_DIR',\n // Needed so the agent can talk to the user's clipboard for the chat image-paste flow\n // (xclip needs DISPLAY/XAUTHORITY, wl-copy needs WAYLAND_DISPLAY).\n 'DISPLAY', 'XAUTHORITY', 'WAYLAND_DISPLAY', 'XDG_SESSION_TYPE',\n];\n\n/**\n * Windows needs its whole native environment, not a POSIX subset. Without\n * PATHEXT a shell cannot resolve `git` to `git.exe`; without SystemRoot/windir\n * half of the OS fails to load; without APPDATA/USERPROFILE git finds neither\n * .gitconfig nor its credential helper. Keep this list broad — these are all\n * OS-provided variables, none of them carry user secrets.\n */\nconst WINDOWS_SAFE_KEYS = [\n 'PATH', 'PATHEXT', 'NODE_ENV', 'TZ', 'TERM',\n // Interpreter + OS roots\n 'ComSpec', 'SystemRoot', 'SystemDrive', 'windir', 'OS', 'PSModulePath',\n // User profile & app data (git config, credential helpers, npm/scoop shims)\n 'USERPROFILE', 'HOMEDRIVE', 'HOMEPATH', 'USERNAME', 'USERDOMAIN',\n 'USERDOMAIN_ROAMINGPROFILE', 'LOGONSERVER', 'APPDATA', 'LOCALAPPDATA',\n // Machine-wide install roots\n 'ProgramData', 'ProgramFiles', 'ProgramFiles(x86)', 'ProgramW6432',\n 'CommonProgramFiles', 'CommonProgramFiles(x86)', 'CommonProgramW6432',\n 'ALLUSERSPROFILE', 'PUBLIC', 'DriverData',\n // Temp\n 'TEMP', 'TMP',\n // Architecture / CPU (used by installers and native module loaders)\n 'PROCESSOR_ARCHITECTURE', 'PROCESSOR_ARCHITEW6432', 'PROCESSOR_IDENTIFIER',\n 'PROCESSOR_LEVEL', 'PROCESSOR_REVISION', 'NUMBER_OF_PROCESSORS',\n // Session / terminal\n 'SESSIONNAME', 'COMPUTERNAME', 'FPS_BROWSER_APP_PROFILE_STRING',\n 'FPS_BROWSER_USER_PROFILE_STRING', 'WT_SESSION', 'WT_PROFILE_ID',\n];\n\n/**\n * Build the environment for the spawned agent process.\n *\n * Keys are copied with the casing they have in `process.env`: Windows exposes\n * `Path`, not `PATH`, and Node's case-insensitive lookup would otherwise let us\n * write a second `PATH` key alongside it — the child would then carry both and\n * the OS would honour only one of them.\n *\n * Returns null if encryption key is not available (setup mode).\n */\nfunction buildAgentEnv(): Record<string, string> | null {\n const safeKeys = process.platform === 'win32' ? WINDOWS_SAFE_KEYS : POSIX_SAFE_KEYS;\n const agentEnv: Record<string, string> = {\n RUNEYA_AGENT_PORT: String(env.RUNEYA_AGENT_PORT),\n RUNEYA_AGENT_HOST: env.RUNEYA_AGENT_HOST,\n };\n\n // Stable passphrase derived from the encryption key\n // (allows committing agents.json without constant changes on restart)\n const passphrase = deriveStableSecret('local-agent-passphrase');\n if (!passphrase) {\n // Encryption key not available — cannot spawn agent\n return null;\n }\n agentEnv.RUNEYA_AGENT_PASSPHRASE = passphrase;\n\n const wanted = new Set(safeKeys.map(k => k.toLowerCase()));\n for (const [key, value] of Object.entries(process.env)) {\n if (value && wanted.has(key.toLowerCase())) {\n agentEnv[key] = value;\n }\n }\n\n return agentEnv;\n}\n\n/**\n * La ligne par laquelle l'agent transmet sa passphrase.\n *\n * Tolère ce qui la précède : un agent d'une autre version peut horodater sa\n * sortie, et un motif ancré en début de ligne ne la reconnaîtrait plus — le\n * serveur attendrait alors son délai entier avant de renoncer, agent pourtant\n * parfaitement démarré. Ce qui suit reste strict : 64 caractères hexadécimaux,\n * puis la fin de ligne.\n */\n/**\n * Relaie une ligne de l'agent sans la dater une seconde fois.\n *\n * L'agent horodate déjà sa sortie, et c'est son temps à lui qui compte pour\n * lire son démarrage. Passer par `console` y ajouterait celui du serveur, et\n * chaque ligne porterait deux compteurs.\n */\nfunction forwardAgentLine(line: string): void {\n process.stdout.write(`[agent] ${line}\\n`);\n}\n\nconst PASSPHRASE_LINE = /(?:^|\\s)RUNEYA_PASSPHRASE=([a-f0-9]{64})$/;\n\nconst LOCAL_AGENT_ID = 'local-agent';\nconst LOCAL_AGENT_NAME = 'local';\n\nclass AgentSpawner {\n private agentProcess: ChildProcess | null = null;\n private exitHandlerRegistered = false;\n private fileWatcher: ReturnType<typeof watch> | null = null;\n private isRestarting = false;\n private agentDistPath: string | null = null;\n\n async spawnLocalAgent(): Promise<void> {\n // Échappatoire pour les bancs d'essai : un serveur qui ne pilote aucun\n // process n'a pas besoin de payer le démarrage d'un second process Node.\n // Sans agent local, seuls les agents distants déclarés répondent.\n if (process.env.RUNEYA_DISABLE_LOCAL_AGENT === '1') {\n console.log('[agent-spawner] RUNEYA_DISABLE_LOCAL_AGENT=1 — aucun agent local ne sera démarré');\n return;\n }\n\n const agentUrl = `http://${env.RUNEYA_AGENT_HOST}:${env.RUNEYA_AGENT_PORT}`;\n\n // Check if encryption key is available\n const agentEnv = buildAgentEnv();\n if (!agentEnv) {\n console.log('[agent-spawner] ⚠️ Cannot spawn local agent: encryption key not available');\n console.log('[agent-spawner] Please provide the encryption key via the setup UI');\n return;\n }\n\n console.log(`[agent-spawner] Spawning local agent on ${agentUrl}...`);\n\n const isDev = process.env.NODE_ENV === 'development';\n // Use AGENT_BINARY_PATH if provided (e.g., from CLI), otherwise resolve\n // relative to this file's location — process.cwd() is unreliable because\n // the server chdir's into the user's stack root when DATA_DIR is set.\n this.agentDistPath = env.AGENT_BINARY_PATH\n || join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'agent', 'dist', 'index.js');\n\n // Always spawn from dist/index.js (built by tsup watch in dev mode)\n this.agentProcess = spawn('node', [this.agentDistPath], {\n env: agentEnv,\n stdio: ['ignore', 'pipe', 'pipe'],\n // Sans ceci, une fenêtre de commande s'ouvre sous Windows dès que ce\n // serveur n'a pas de console à prêter — ce qui est le cas après un\n // redémarrage détaché (diagnostics, mise à jour) : Windows alloue alors\n // une console AU FILS, et une console allouée est une fenêtre visible.\n windowsHide: true,\n });\n\n // Publié au registre pour qu'une autre instance sache quoi arrêter : tuer\n // ce serveur ne réclame pas l'agent, qui n'est pas dans son groupe de\n // processus — et sur Windows il n'y a pas de groupe du tout.\n void setInstanceAgentPid(this.agentProcess.pid ?? null);\n\n if (isDev) {\n // In dev mode, watch dist/index.js and restart agent when it changes\n // (tsup --watch rebuilds this file when src/ changes)\n this.setupFileWatcher();\n }\n\n // Safety net: kill agent synchronously when server process exits for any reason\n // (e.g. Ctrl+C via Turbo/tsup where async shutdown may not complete)\n if (!this.exitHandlerRegistered) {\n this.exitHandlerRegistered = true;\n process.on('exit', () => {\n if (this.agentProcess && !this.agentProcess.killed) {\n this.agentProcess.kill('SIGKILL');\n }\n });\n }\n\n // Parse passphrase from stdout\n const passphrase = await this.waitForPassphrase();\n if (!passphrase) {\n console.error('[agent-spawner] Failed to get passphrase from local agent');\n return;\n }\n\n console.log('[agent-spawner] Local agent started, passphrase received');\n\n // Register the local agent (in-memory only — no persistence to agents.json)\n await agentManager.registerLocal({ id: LOCAL_AGENT_ID, name: LOCAL_AGENT_NAME, url: agentUrl, passphrase, passthroughEnv: [] });\n console.log(`[agent-spawner] Local agent registered as ${LOCAL_AGENT_ID}`);\n }\n\n private waitForPassphrase(): Promise<string | null> {\n return new Promise((resolve) => {\n if (!this.agentProcess?.stdout) {\n resolve(null);\n return;\n }\n\n const rl = createInterface({ input: this.agentProcess.stdout });\n const timeout = setTimeout(() => {\n resolve(null);\n }, PASSPHRASE_TIMEOUT);\n\n let initialPassphraseFound = false;\n\n rl.on('line', (line) => {\n const match = PASSPHRASE_LINE.exec(line);\n if (match) {\n const newPassphrase = match[1];\n\n if (!initialPassphraseFound) {\n // First passphrase - resolve the promise to complete initial spawn\n initialPassphraseFound = true;\n clearTimeout(timeout);\n resolve(newPassphrase);\n } else {\n // Agent reloaded (tsx watch) - update the passphrase\n console.log('[agent-spawner] 🔄 Agent reloaded, updating passphrase...');\n this.updateAgentPassphrase(newPassphrase).catch((err: Error) => {\n console.error('[agent-spawner] Failed to update passphrase after reload:', err);\n });\n }\n // Don't print the passphrase line\n return;\n }\n // Print all non-passphrase agent stdout\n forwardAgentLine(line);\n });\n\n // Forward stderr permanently\n if (this.agentProcess?.stderr) {\n const stderrRl = createInterface({ input: this.agentProcess.stderr });\n stderrRl.on('line', (line) => {\n console.error(`[agent:err] ${line}`);\n });\n }\n\n this.agentProcess?.on('error', (err) => {\n console.error('[agent-spawner] Failed to spawn agent:', err);\n clearTimeout(timeout);\n if (!initialPassphraseFound) resolve(null);\n });\n\n this.agentProcess?.on('exit', (code) => {\n if (code !== null && code !== 0) {\n console.error(`[agent-spawner] Agent exited with code ${code}`);\n }\n clearTimeout(timeout);\n if (!initialPassphraseFound) resolve(null);\n });\n });\n }\n\n async shutdown(): Promise<void> {\n if (this.fileWatcher) {\n this.fileWatcher.close();\n this.fileWatcher = null;\n }\n\n if (this.agentProcess) {\n console.log('[agent-spawner] Stopping local agent...');\n // SIGKILL for immediate port release — the local agent is stateless\n // so graceful shutdown is unnecessary.\n this.agentProcess.kill('SIGKILL');\n\n await new Promise<void>((resolve) => {\n this.agentProcess?.on('exit', () => resolve());\n // Safety timeout in case 'exit' never fires\n setTimeout(resolve, 2_000);\n });\n\n this.agentProcess = null;\n void setInstanceAgentPid(null);\n }\n }\n\n getLocalAgentId(): string | null {\n return LOCAL_AGENT_ID;\n }\n\n private async updateAgentPassphrase(newPassphrase: string): Promise<void> {\n const agentUrl = `http://${env.RUNEYA_AGENT_HOST}:${env.RUNEYA_AGENT_PORT}`;\n await agentManager.registerLocal({ id: LOCAL_AGENT_ID, name: LOCAL_AGENT_NAME, url: agentUrl, passphrase: newPassphrase, passthroughEnv: [] });\n console.log(`[agent-spawner] ✅ Agent passphrase updated, re-registered as ${LOCAL_AGENT_ID}`);\n }\n\n private setupFileWatcher(): void {\n if (!this.agentDistPath) return;\n\n // Close previous watcher if it exists (important for hot reload to keep working)\n if (this.fileWatcher) {\n this.fileWatcher.close();\n }\n\n console.log('[agent-spawner] 🔍 Watching agent dist for changes (hot reload enabled)');\n\n // Watch the dist/ directory instead of the file itself — tsup \"cleans\" the folder\n // (delete + recreate) on every rebuild, which causes fs.watch on a file to lose\n // its inode and stop firing. Watching the directory captures rename/add events too.\n const distDir = join(this.agentDistPath, '..');\n this.fileWatcher = watch(distDir, { persistent: false }, (eventType, filename) => {\n if ((eventType === 'change' || eventType === 'rename') && filename === 'index.js' && !this.isRestarting) {\n this.isRestarting = true;\n console.log('[agent-spawner] 🔄 Agent code changed, restarting...');\n\n // In dev mode, kill immediately for fast hot reload (no graceful shutdown needed)\n if (this.agentProcess && !this.agentProcess.killed) {\n const oldProcess = this.agentProcess;\n\n // Wait for process to fully exit before restarting\n oldProcess.once('exit', () => {\n // Give OS a moment to release the port\n setTimeout(() => {\n this.restartAgent().catch((err: Error) => {\n console.error('[agent-spawner] Failed to restart agent:', err);\n this.isRestarting = false;\n });\n }, 100);\n });\n\n // SIGKILL immediately for instant hot reload\n oldProcess.kill('SIGKILL');\n }\n }\n });\n this.fileWatcher.on('error', (err: NodeJS.ErrnoException) => {\n console.error('[agent-spawner] File watcher error:', err.message);\n });\n }\n\n private async restartAgent(): Promise<void> {\n if (!this.agentDistPath) return;\n\n const agentEnv = buildAgentEnv();\n if (!agentEnv) {\n console.log('[agent-spawner] Cannot restart agent: encryption key not available');\n this.isRestarting = false;\n return;\n }\n\n const agentUrl = `http://${env.RUNEYA_AGENT_HOST}:${env.RUNEYA_AGENT_PORT}`;\n const isDev = process.env.NODE_ENV === 'development';\n\n // Spawn new agent process\n this.agentProcess = spawn('node', [this.agentDistPath], {\n env: agentEnv,\n stdio: ['ignore', 'pipe', 'pipe'],\n // Sans ceci, une fenêtre de commande s'ouvre sous Windows dès que ce\n // serveur n'a pas de console à prêter — ce qui est le cas après un\n // redémarrage détaché (diagnostics, mise à jour) : Windows alloue alors\n // une console AU FILS, et une console allouée est une fenêtre visible.\n windowsHide: true,\n });\n\n // Setup stdout/stderr forwarding and passphrase detection\n if (this.agentProcess?.stdout) {\n const rl = createInterface({ input: this.agentProcess.stdout });\n rl.on('line', (line) => {\n const match = PASSPHRASE_LINE.exec(line);\n if (match) {\n const newPassphrase = match[1];\n // Update agent registration with new passphrase (in-memory only)\n agentManager.registerLocal({ id: LOCAL_AGENT_ID, name: LOCAL_AGENT_NAME, url: agentUrl, passphrase: newPassphrase, passthroughEnv: [] })\n .then(() => {\n console.log(`[agent-spawner] ✅ Agent restarted and re-registered as ${LOCAL_AGENT_ID}`);\n this.isRestarting = false;\n\n // Recreate file watcher for next hot reload (fs.watch can lose tracking after file replace)\n if (isDev) {\n this.setupFileWatcher();\n }\n })\n .catch((err: Error) => {\n console.error('[agent-spawner] Failed to register agent after restart:', err);\n this.isRestarting = false;\n });\n return;\n }\n forwardAgentLine(line);\n });\n }\n\n if (this.agentProcess?.stderr) {\n const stderrRl = createInterface({ input: this.agentProcess.stderr });\n stderrRl.on('line', (line) => {\n console.error(`[agent:err] ${line}`);\n });\n }\n }\n}\n\nexport const agentSpawner = new AgentSpawner();\n","import { mkdir, readdir, readFile, writeFile, rm } from 'node:fs/promises';\nimport { readFileSync } from 'node:fs';\nimport dotenv from 'dotenv';\nimport { spawn } from 'node:child_process';\nimport { join } from 'node:path';\nimport { z } from 'zod';\nimport { env } from '../config/env.js';\nimport { machineRoot } from './vault-resolver.js';\n\n/**\n * Registry of the Runeya instances running on this machine.\n *\n * Each instance drops a small file named after its pid and removes it on the\n * way out. Discovery by scanning the process table was the alternative, and it\n * is worse on every axis: `ps` output differs per platform, an instance run\n * through a wrapper is invisible, and the command line does not carry the data\n * that actually matters here — which port answers, which directory it serves.\n *\n * The file is a claim, not proof. Anything that crashed or was SIGKILLed\n * leaves its file behind, so every record is checked twice before it is shown:\n * the process must still exist, and its port must answer.\n *\n * Both checks are needed, and neither is enough alone. Asking only the port\n * confirms that *a* Runeya answers there, not that it is this one — restart an\n * instance on the same port and the dead record inherits the live one's answer,\n * uptime included. Asking only the pid misses a process that is alive but no\n * longer serving.\n */\n\nconst InstanceFileSchema = z.object({\n pid: z.number().int().positive(),\n /**\n * The agent this server spawned, when it has one.\n *\n * Optional because an instance started before this field existed never wrote\n * it, and because the agent may not be up yet — or at all, when the\n * encryption key is missing. Absent means « unknown », never « none ».\n */\n agentPid: z.number().int().positive().optional(),\n port: z.number().int().min(1).max(65535),\n agentPort: z.number().int().min(1).max(65535),\n cwd: z.string().max(4096),\n dataDir: z.string().max(4096),\n version: z.string().max(64),\n fromDesktop: z.boolean(),\n startedAt: z.number().int().nonnegative(),\n /**\n * De quoi relancer ce lancement à l'identique : l'exécutable, ses arguments,\n * et les `RUNEYA_*` de son environnement. Le `cwd` ci-dessus complète le\n * tableau.\n *\n * Optionnels, comme `agentPid` : une instance antérieure à ce champ ne les\n * écrit pas. Elle reste listée et arrêtable, simplement pas redémarrable —\n * un redémarrage approximatif serait pire qu'un bouton absent.\n */\n execPath: z.string().max(4096).optional(),\n argv: z.array(z.string().max(4096)).max(128).optional(),\n env: z.record(z.string().max(64), z.string().max(4096)).optional(),\n /**\n * L'instance tient un secret (`RUNEYA_ENCRYPTION_KEY`,\n * `RUNEYA_MASTER_PASSWORD`) que le relancement ne retrouverait pas. Le\n * drapeau, jamais la valeur : le registre est un fichier lisible, et ces\n * deux-là n'ont délibérément pas de flag CLI parce qu'`argv` se lit dans\n * `ps`.\n *\n * Un secret posé dans le `.env` de la pile ne compte pas : `dotenv` le relit\n * au démarrage depuis le même répertoire. Voir `secretsAreReproducible()`.\n */\n needsSecretEnv: z.boolean().optional(),\n});\n\nexport type InstanceFile = z.infer<typeof InstanceFileSchema>;\n\nexport type DetectedInstance = InstanceFile & {\n /** The port answered a health check just now. */\n reachable: boolean;\n /** This very process. */\n current: boolean;\n /** Uptime reported by the instance itself, in milliseconds — health.check's unit. */\n uptimeMs: number | null;\n /** Le lancement est reproductible : redémarrage possible. */\n restartable: boolean;\n /** Pourquoi il ne l'est pas, quand il ne l'est pas. */\n restartBlockedBy: RestartBlocker | null;\n};\n\n/**\n * - `legacy` : lancée avant que le registre ne publie de quoi la relancer.\n * - `secrets` : son environnement portait un secret que le registre n'a pas le\n * droit de conserver ; le lancement ne peut pas être reproduit à l'identique.\n *\n * Être l'instance courante n'en fait pas partie : elle se redémarre par un\n * autre chemin (voir `scheduleSelfRestart`), mais elle se redémarre.\n */\nexport type RestartBlocker = 'legacy' | 'secrets';\n\nfunction registryDir(): string {\n // Machine-level, not project-level: the point is to see every Runeya on the\n // box, including those serving other directories. `machineRoot()` reads the\n // environment on each call, which is what lets the test suite redirect it.\n return join(machineRoot(), 'instances');\n}\n\nfunction fileFor(pid: number): string {\n return join(registryDir(), `${pid}.json`);\n}\n\n/**\n * Ce que cette instance a publié. Gardé en mémoire pour que la mise à jour du\n * pid de l'agent n'ait pas à relire — et donc à faire confiance à — son propre\n * fichier, qu'un tiers pourrait avoir réécrit.\n */\nlet published: InstanceFile | null = null;\n\n/**\n * Les variables que le registre ne recopie jamais. Voir `needsSecretEnv`.\n */\nconst SECRET_ENV_KEYS = ['RUNEYA_ENCRYPTION_KEY', 'RUNEYA_MASTER_PASSWORD'] as const;\n\nfunction isSecretEnvKey(key: string): boolean {\n return (SECRET_ENV_KEYS as readonly string[]).includes(key);\n}\n\n/**\n * Les `RUNEYA_*` de cet environnement, secrets exclus.\n *\n * Seul ce préfixe est retenu : tout ce que Runeya lit le porte (cf. `env.ts`),\n * et recopier l'environnement entier reviendrait à figer dans un fichier le\n * `.env` de la pile gérée, jetons compris.\n */\nfunction runeyaEnv(): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(process.env)) {\n if (!key.startsWith('RUNEYA_') || isSecretEnvKey(key)) continue;\n if (typeof value === 'string' && value.length <= 4096) out[key] = value;\n }\n return out;\n}\n\n/**\n * Un secret présent dans l'environnement empêche-t-il de rejouer ce lancement ?\n *\n * Pas toujours, et la nuance est celle qui décide si le bouton est cliquable.\n * `RUNEYA_MASTER_PASSWORD` posé dans le `.env` de la pile n'est pas hérité du\n * shell : `dotenv` le relit au démarrage, depuis ce même répertoire. Le\n * relancer le retrouve donc tout seul, sans que le registre ait eu à le copier.\n *\n * Ce qui n'est pas reproductible, c'est le secret venu d'ailleurs — exporté\n * dans le shell, injecté par un CI. La comparaison porte sur la valeur, et pas\n * seulement sur la présence de la clé : `dotenv` n'écrase pas une variable déjà\n * définie, donc une clé présente des deux côtés avec deux valeurs différentes\n * signifie que c'est celle du shell qui a gagné.\n */\nfunction secretsAreReproducible(): boolean {\n const present = SECRET_ENV_KEYS.filter((key) => Boolean(process.env[key]));\n if (present.length === 0) return true;\n let fromFile: Record<string, string>;\n try {\n fromFile = dotenv.parse(readFileSync(join(process.cwd(), '.env')));\n } catch {\n // Pas de `.env` lisible : le secret ne peut venir que de l'environnement.\n return false;\n }\n return present.every((key) => fromFile[key] === process.env[key]);\n}\n\nfunction selfRecord(): InstanceFile {\n return {\n pid: process.pid,\n port: env.PORT,\n agentPort: env.RUNEYA_AGENT_PORT,\n cwd: process.cwd(),\n dataDir: env.DATA_DIR,\n version: process.env['RUNEYA_VERSION'] ?? 'unknown',\n fromDesktop: env.FROM_DESKTOP,\n startedAt: Date.now(),\n execPath: process.execPath,\n // argv[0] est l'exécutable, déjà repris par `execPath` ; le reste est le\n // script suivi de ses arguments, c'est-à-dire ce qu'il faut repasser à spawn.\n argv: process.argv.slice(1),\n env: runeyaEnv(),\n needsSecretEnv: !secretsAreReproducible(),\n };\n}\n\n/**\n * Announce this instance, and arrange for the file to go away.\n *\n * `exit` handlers must be synchronous, and unlinking there is unreliable\n * anyway, so the file is treated as disposable: a stale one is detected by the\n * health probe rather than prevented.\n */\nexport async function registerInstance(): Promise<void> {\n try {\n await mkdir(registryDir(), { recursive: true });\n published = selfRecord();\n await writeFile(fileFor(process.pid), JSON.stringify(published, null, 2));\n } catch (err) {\n // A machine whose home is read-only still runs Runeya fine; it just does\n // not show up in the listing.\n console.log('[instances] Could not register this instance:', (err as Error).message);\n }\n}\n\nexport async function unregisterInstance(): Promise<void> {\n await rm(fileFor(process.pid), { force: true }).catch(() => { /* best effort */ });\n}\n\n/**\n * Publier le pid de l'agent local, ou l'effacer quand il n'y en a plus.\n *\n * Sans lui, arrêter une instance depuis une autre laisserait son agent orphelin\n * sur Windows, où tuer un parent ne réclame pas ses enfants. En développement\n * l'agent redémarre à chaque rebuild avec un pid neuf, d'où un appel à chaque\n * spawn plutôt qu'un seul au démarrage.\n */\nexport async function setInstanceAgentPid(agentPid: number | null): Promise<void> {\n if (!published) return;\n published = { ...published, agentPid: agentPid ?? undefined };\n try {\n await writeFile(fileFor(process.pid), JSON.stringify(published, null, 2));\n } catch {\n // Une instance qui ne peut pas réécrire son fichier reste utilisable ;\n // seule l'extinction à distance de son agent en pâtit.\n }\n}\n\n/**\n * Whether the process still exists. Signal 0 performs the permission and\n * existence checks without delivering anything; EPERM means it exists but\n * belongs to another user, which still counts as alive.\n */\nfunction isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === 'EPERM';\n }\n}\n\n/** Ask a port whether a Runeya answers there. */\nasync function probe(port: number): Promise<{ ok: boolean; uptimeMs: number | null; version: string | null }> {\n try {\n const res = await fetch(`http://127.0.0.1:${port}/api/trpc/health.check`, {\n signal: AbortSignal.timeout(1500),\n });\n if (!res.ok) return { ok: false, uptimeMs: null, version: null };\n const body = await res.json() as { result?: { data?: { uptime?: number; version?: string } } };\n const data = body.result?.data;\n // health.check reports `Date.now() - startedAt`, i.e. milliseconds.\n return { ok: true, uptimeMs: data?.uptime ?? null, version: data?.version ?? null };\n } catch {\n return { ok: false, uptimeMs: null, version: null };\n }\n}\n\n/**\n * List the instances this machine knows about.\n *\n * Unreachable entries are dropped *and* their file removed: a listing that\n * keeps showing a Runeya that died last week is worse than one that shows\n * nothing, because it invites the user to go looking for it.\n */\n/**\n * Ce qu'on peut dire d'une instance à partir de son seul pid.\n *\n * Assez pour la NOMMER — c'est son port qu'on cherche, pas son dossier : c'est\n * par là qu'on l'ouvre, et c'est ce qui la distingue de ses voisines pour qui\n * la regarde. Volontairement sans sonde réseau, contrairement à `listInstances` :\n * l'appelant a déjà, par ailleurs, une raison de croire ce pid vivant.\n *\n * `null` quand le pid n'a rien publié : une instance antérieure au registre, ou\n * un tiers. Le dire vaut mieux qu'inventer.\n */\nexport async function describeInstance(pid: number): Promise<{ pid: number; port: number; cwd: string } | null> {\n try {\n const parsed = InstanceFileSchema.safeParse(JSON.parse(await readFile(fileFor(pid), 'utf8')));\n if (!parsed.success) return null;\n return { pid, port: parsed.data.port, cwd: parsed.data.cwd };\n } catch {\n return null;\n }\n}\n\nexport async function listInstances(): Promise<DetectedInstance[]> {\n let files: string[];\n try {\n files = await readdir(registryDir());\n } catch {\n return [];\n }\n\n const found = await Promise.all(\n files\n .filter((f) => f.endsWith('.json'))\n .map(async (name): Promise<DetectedInstance | null> => {\n const path = join(registryDir(), name);\n let record: InstanceFile;\n try {\n const parsed = InstanceFileSchema.safeParse(JSON.parse(await readFile(path, 'utf8')));\n if (!parsed.success) {\n await rm(path, { force: true }).catch(() => { /* best effort */ });\n return null;\n }\n record = parsed.data;\n } catch {\n return null;\n }\n\n // Cheapest check first, and the one that catches the common case: an\n // instance killed or crashed never removes its own file.\n if (!isProcessAlive(record.pid)) {\n await rm(path, { force: true }).catch(() => { /* best effort */ });\n return null;\n }\n\n const { ok, uptimeMs, version } = await probe(record.port);\n if (!ok) {\n await rm(path, { force: true }).catch(() => { /* best effort */ });\n return null;\n }\n\n const current = record.pid === process.pid;\n const blocker = restartBlockerFor(record);\n return {\n ...record,\n // Trust the live answer over the file: the file was written at boot\n // and an upgraded instance would keep claiming its old version.\n version: version ?? record.version,\n reachable: true,\n current,\n uptimeMs,\n restartable: blocker === null,\n restartBlockedBy: blocker,\n };\n }),\n );\n\n return dedupeByPort(found.filter((i): i is DetectedInstance => i !== null))\n .sort((a, b) => Number(b.current) - Number(a.current) || a.port - b.port);\n}\n\n/**\n * One port, one instance.\n *\n * Two live records can still claim the same port when a pid has been recycled\n * onto an unrelated process — the file then passes both checks while its\n * Runeya is long gone. Only one of them can actually be listening, and it is\n * the one that started last, so that is the one kept.\n */\nfunction dedupeByPort(instances: DetectedInstance[]): DetectedInstance[] {\n const byPort = new Map<number, DetectedInstance>();\n for (const instance of instances) {\n const kept = byPort.get(instance.port);\n // The current process is authoritative about its own port.\n if (!kept || instance.current || (!kept.current && instance.startedAt > kept.startedAt)) {\n byPort.set(instance.port, instance);\n }\n }\n return [...byPort.values()];\n}\n\n/**\n * Ce qui empêche de relancer ce lancement à l'identique, s'il y a lieu.\n *\n * Ne dit rien de *comment* on relance : l'instance courante passe par un\n * relanceur détaché plutôt que par un arrêt suivi d'un spawn, mais les deux\n * chemins exigent le même lancement reproductible.\n */\nfunction restartBlockerFor(record: InstanceFile): RestartBlocker | null {\n if (record.needsSecretEnv) return 'secrets';\n if (!record.execPath || !record.argv) return 'legacy';\n return null;\n}\n\n/**\n * Arrêter une instance Runeya de cette machine, son agent d'abord.\n *\n * L'ordre n'est pas cosmétique. L'agent est un processus distinct, lancé sans\n * groupe de processus : sur Windows, tuer le serveur ne réclame pas ses\n * enfants, et l'agent survivrait en tenant son port. On le termine donc\n * explicitement, puis le serveur.\n *\n * Le pid visé ne vient jamais de l'appelant : il est cherché dans la liste que\n * `listInstances()` vient de valider — le processus existe, et son port répond\n * bien à `health.check`. Un pid quelconque, recyclé ou inventé, n'y figure pas\n * et n'est donc jamais signalé.\n *\n * Tout passe par `process.kill`, que Node porte sur les trois systèmes ; aucune\n * commande externe (`kill`, `pkill`, `taskkill`) n'est appelée. Sur Windows les\n * signaux n'existent pas et la terminaison est immédiate : le SIGTERM y est\n * déjà brutal, et l'escalade qui suit devient sans objet.\n */\nexport type KillOutcome = 'terminated' | 'killed' | 'absent' | 'denied' | 'failed';\n\nexport interface KillInstanceResult {\n agent: KillOutcome;\n server: KillOutcome;\n}\n\n/** Laissé à un processus pour sortir de lui-même avant l'escalade. */\nconst GRACE_MS = 5_000;\nconst POLL_MS = 100;\n\nasync function terminate(pid: number): Promise<KillOutcome> {\n if (!isProcessAlive(pid)) return 'absent';\n try {\n process.kill(pid, 'SIGTERM');\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ESRCH') return 'absent';\n if (code === 'EPERM') return 'denied';\n return 'failed';\n }\n\n const deadline = Date.now() + GRACE_MS;\n while (Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, POLL_MS));\n if (!isProcessAlive(pid)) return 'terminated';\n }\n\n // Le délai de grâce a passé : le processus est bloqué, ou ignore SIGTERM.\n try {\n process.kill(pid, 'SIGKILL');\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === 'ESRCH' ? 'terminated' : 'failed';\n }\n await new Promise((resolve) => setTimeout(resolve, POLL_MS));\n return isProcessAlive(pid) ? 'failed' : 'killed';\n}\n\nexport async function killInstance(pid: number): Promise<KillInstanceResult> {\n const target = (await listInstances()).find((i) => i.pid === pid);\n if (!target) throw new Error('instance-not-found');\n // Se tuer soi-même couperait la réponse en cours d'envoi : la page n'aurait\n // aucun moyen de distinguer l'extinction réussie de la requête perdue.\n if (target.current) throw new Error('instance-is-current');\n\n const agent = target.agentPid ? await terminate(target.agentPid) : 'absent';\n const server = await terminate(pid);\n\n // Le fichier du registre survit à un SIGKILL, qui ne laisse rien se\n // désinscrire. Le retirer ici évite d'attendre le prochain balayage.\n if (server === 'terminated' || server === 'killed') {\n await rm(fileFor(pid), { force: true }).catch(() => { /* best effort */ });\n }\n return { agent, server };\n}\n\n/**\n * Redémarrer une instance : l'arrêter, attendre que son port se libère, puis\n * relancer exactement le même lancement.\n *\n * « Exactement » est la seule promesse qui vaille ici. Le registre publie\n * l'exécutable, les arguments, le répertoire de lancement et les `RUNEYA_*` de\n * l'environnement ; ce qu'il ne peut pas publier — les deux secrets — bloque le\n * redémarrage plutôt que de le rendre approximatif. Une instance relancée sans\n * sa clé démarrerait sans agent, et le bouton aurait menti.\n *\n * Le processus est détaché, et ce n'est pas cosmétique : sans `detached`, la\n * ressuscitée devient fille de l'instance qui a cliqué et mourrait avec elle.\n * Ses flux sont ignorés pour la même raison — un pipe non lu finit par bloquer\n * l'écrivain, et personne n'est là pour le vider.\n *\n * L'attente sur le port n'est pas non plus une précaution de confort : relancer\n * trop tôt, c'est soit `EADDRINUSE`, soit — pire — un glissement silencieux vers\n * un autre port par `resolvePort`, et ce n'est alors plus le même lancement.\n */\n/** Un lancement prêt à être rejoué, tel que le registre l'a publié. */\ninterface Launch {\n execPath: string;\n argv: string[];\n cwd: string;\n env: Record<string, string>;\n}\n\n/**\n * Reconstituer le lancement d'une instance.\n *\n * L'environnement du fichier fait autorité, pas celui de cette instance : les\n * `RUNEYA_*` d'ici décrivent *ce* serveur, et les laisser passer relancerait la\n * voisine avec nos ports. Les secrets d'ici sont retirés pour la même raison —\n * la cible n'en avait pas, sinon elle serait bloquée. Le reste de\n * l'environnement (PATH, HOME…) est celui de la machine et se transmet tel quel.\n */\nfunction launchOf(record: InstanceFile): Launch {\n if (!record.execPath || !record.argv) throw new Error('instance-not-restartable:legacy');\n const env: Record<string, string> = {};\n for (const [key, value] of Object.entries(process.env)) {\n if (typeof value === 'string' && !key.startsWith('RUNEYA_')) env[key] = value;\n }\n return {\n execPath: record.execPath,\n argv: record.argv,\n cwd: record.cwd,\n env: { ...env, ...record.env },\n };\n}\n\nexport interface RestartInstanceResult {\n stop: KillInstanceResult;\n /** Le pid du processus relancé. */\n pid: number;\n /** Le port a bien été rendu avant le relancement. */\n portReleased: boolean;\n}\n\n/** Au-delà, le port n'est visiblement pas près d'être rendu. */\nconst PORT_RELEASE_TIMEOUT_MS = 15_000;\n\nasync function waitForPortRelease(port: number): Promise<boolean> {\n const deadline = Date.now() + PORT_RELEASE_TIMEOUT_MS;\n while (Date.now() < deadline) {\n const { ok } = await probe(port);\n if (!ok) return true;\n await new Promise((resolve) => setTimeout(resolve, POLL_MS));\n }\n return false;\n}\n\nexport async function restartInstance(pid: number): Promise<RestartInstanceResult> {\n const target = (await listInstances()).find((i) => i.pid === pid);\n if (!target) throw new Error('instance-not-found');\n if (target.restartBlockedBy) throw new Error(`instance-not-restartable:${target.restartBlockedBy}`);\n // Une instance ne s'arrête pas elle-même pour se relancer : elle passe par\n // `scheduleSelfRestart`, qui répond d'abord et pose un relanceur.\n if (target.current) throw new Error('instance-is-current');\n\n const stop = await killInstance(pid);\n if (stop.server !== 'terminated' && stop.server !== 'killed') {\n throw new Error(`instance-stop-${stop.server}`);\n }\n\n const portReleased = await waitForPortRelease(target.port);\n\n const launch = launchOf(target);\n const child = spawn(launch.execPath, launch.argv, {\n cwd: launch.cwd,\n env: launch.env,\n detached: true,\n stdio: 'ignore',\n // `detached` demande à Windows une console neuve, et une console neuve est\n // une fenêtre de commande à l'écran. Le lanceur de bureau cache la sienne :\n // sans ceci, relancer une instance la ferait réapparaître.\n windowsHide: true,\n });\n // Un `spawn` échoue de manière asynchrone (répertoire disparu, exécutable\n // déplacé depuis le lancement). Sans écouteur, l'erreur remonterait en\n // exception non capturée et tuerait *cette* instance — celle qui rendait\n // service.\n child.on('error', (err) => {\n console.log(`[instances] Relance du pid ${pid} impossible :`, err.message);\n });\n child.unref();\n\n if (typeof child.pid !== 'number') throw new Error('instance-restart-failed');\n return { stop, pid: child.pid, portReleased };\n}\n\n/**\n * Le relanceur : un processus de rien du tout, dont le seul travail est\n * d'attendre que le port soit rendu, puis de rejouer le lancement.\n *\n * Il existe parce qu'une instance ne peut pas se relancer elle-même sans\n * course. Lancer le successeur avant de mourir, c'est lui offrir un port encore\n * tenu — `resolvePort` le fait alors glisser ailleurs en silence, et le\n * lancement n'est plus le même, ce qui est précisément la promesse à tenir ici.\n * Mourir avant de lancer, c'est n'avoir plus personne pour lancer.\n *\n * Sa charge passe par une variable d'environnement, jamais par `argv` : c'est\n * la même raison qui prive les deux secrets de flag CLI — `ps` lit les\n * arguments de tout le monde.\n */\nconst RELAUNCHER = `\n const p = JSON.parse(process.env.RUNEYA_RELAUNCH_PAYLOAD);\n const deadline = Date.now() + p.timeoutMs;\n const held = async () => {\n try {\n const res = await fetch('http://127.0.0.1:' + p.port + '/api/trpc/health.check', {\n signal: AbortSignal.timeout(500),\n });\n return res.ok;\n } catch { return false; }\n };\n (async () => {\n while (Date.now() < deadline && await held()) {\n await new Promise((r) => setTimeout(r, ${POLL_MS}));\n }\n require('node:child_process')\n .spawn(p.execPath, p.argv, { cwd: p.cwd, env: p.env, detached: true, stdio: 'ignore', windowsHide: true })\n .unref();\n })();\n`;\n\n/**\n * Se redémarrer soi-même : poster un relanceur, puis s'arrêter proprement.\n *\n * L'arrêt est différé d'un battement, et c'est tout l'intérêt de séparer cette\n * fonction de `restartInstance` : la réponse tRPC doit avoir quitté la socket\n * avant que le processus ne parte, faute de quoi la page ne saurait pas\n * distinguer un redémarrage réussi d'une requête perdue.\n *\n * On se signale, on ne se tue pas : `SIGTERM` passe par l'arrêt gracieux\n * habituel — agent terminé, fichier de registre retiré — que `process.exit()`\n * court-circuiterait.\n */\nexport interface SelfRestartResult {\n /** Le relanceur, pas le futur serveur : son pid n'est pas celui qui reviendra. */\n relauncherPid: number;\n /** Port sur lequel l'instance est attendue de retour. */\n port: number;\n}\n\n/** Délai laissé à la réponse pour partir avant que le processus ne s'arrête. */\nconst SELF_RESTART_DELAY_MS = 250;\n\n/**\n * Poster le relanceur d'un lancement donné, sans rien arrêter.\n *\n * Séparé de `scheduleSelfRestart` pour être vérifiable : le relanceur est la\n * pièce délicate — il attend un port et relance — et un test ne peut pas\n * s'arrêter lui-même pour l'éprouver.\n */\nexport function spawnRelauncher(record: InstanceFile): number {\n const blocker = restartBlockerFor(record);\n if (blocker) throw new Error(`instance-not-restartable:${blocker}`);\n\n const launch = launchOf(record);\n const relauncher = spawn(process.execPath, ['-e', RELAUNCHER], {\n cwd: launch.cwd,\n // Une console neuve serait une fenêtre de commande à l'écran, le temps que\n // le relanceur attende la libération du port. Voir `restartInstance`.\n windowsHide: true,\n env: {\n ...launch.env,\n RUNEYA_RELAUNCH_PAYLOAD: JSON.stringify({ ...launch, port: record.port, timeoutMs: PORT_RELEASE_TIMEOUT_MS }),\n },\n detached: true,\n stdio: 'ignore',\n });\n relauncher.on('error', (err) => {\n console.log('[instances] Relanceur impossible à lancer :', err.message);\n });\n relauncher.unref();\n if (typeof relauncher.pid !== 'number') throw new Error('instance-restart-failed');\n return relauncher.pid;\n}\n\nexport async function scheduleSelfRestart(): Promise<SelfRestartResult> {\n const record = published ?? selfRecord();\n const relauncherPid = spawnRelauncher(record);\n\n setTimeout(() => {\n console.log('[instances] Redémarrage demandé : arrêt de cette instance.');\n process.kill(process.pid, 'SIGTERM');\n }, SELF_RESTART_DELAY_MS);\n\n return { relauncherPid, port: record.port };\n}\n","import { stableEncryptValue, decryptValue, isEncryptedValue } from './crypto-helpers.js';\n\nclass TraefikManager {\n encryptDnsCredentials(credentials: Record<string, string>): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(credentials)) {\n result[key] = isEncryptedValue(value) ? value : stableEncryptValue(value);\n }\n return result;\n }\n\n decryptDnsCredentials(credentials: Record<string, string>): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(credentials)) {\n if (isEncryptedValue(value)) {\n try {\n result[key] = decryptValue(value);\n } catch {\n console.error(`[traefik-manager] Failed to decrypt credential: ${key}`);\n result[key] = '';\n }\n } else {\n result[key] = value;\n }\n }\n return result;\n }\n}\n\nexport const traefikManager = new TraefikManager();\n","import { EventEmitter } from 'node:events';\nimport WebSocket from 'ws';\nimport { WS_RECONNECT_MIN, WS_RECONNECT_MAX } from '@runeya/packages-shared';\n\n/**\n * tRPC WS JSON-RPC protocol message types:\n * → { id, method: \"subscription\", params: { path, input } }\n * ← { id, result: { type: \"started\" } }\n * ← { id, result: { type: \"data\", data } }\n * ← { id, result: { type: \"stopped\" } }\n */\n\ninterface TRPCRequest {\n id: number;\n method: 'subscription';\n params: { path: string; input?: unknown };\n}\n\ninterface TRPCResponse {\n id: number;\n result?: {\n type: 'started' | 'data' | 'stopped';\n data?: unknown;\n };\n error?: unknown;\n}\n\ninterface SubscriptionInfo {\n eventType?: string; // e.g. 'process:status' — for static fan-out subscriptions\n onData?: (data: unknown) => void; // for dynamic per-session subscriptions\n onStop?: () => void;\n}\n\nconst SUBSCRIPTION_PATHS: Array<{ path: string; eventType: string }> = [\n { path: 'process.onStatus', eventType: 'process:status' },\n { path: 'process.onLog', eventType: 'process:log' },\n { path: 'process.onHealth', eventType: 'process:health' },\n { path: 'process.onMetrics', eventType: 'process:metrics' },\n];\n\n/**\n * AgentWSBridge opens a WebSocket connection to an agent's tRPC endpoint\n * and subscribes to all process-related subscriptions using the raw\n * tRPC JSON-RPC protocol. Events are emitted with the agentId attached.\n */\nconst PING_INTERVAL_MS = 30_000;\nconst PONG_TIMEOUT_MS = 10_000;\n\nexport class AgentWSBridge extends EventEmitter {\n private ws: WebSocket | null = null;\n private nextId = 1;\n private subscriptions: Map<number, SubscriptionInfo> = new Map();\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private currentReconnectDelay = WS_RECONNECT_MIN;\n private closed = false;\n private pingInterval: ReturnType<typeof setInterval> | null = null;\n private pongTimeout: ReturnType<typeof setTimeout> | null = null;\n private connectionErrorLogged = false;\n\n constructor(\n private agentId: string,\n private url: string,\n private passphrase: string,\n ) {\n super();\n }\n\n connect(): void {\n if (this.closed) return;\n this.cleanup();\n\n const httpUrl = new URL(this.url);\n const wsProtocol = httpUrl.protocol === 'https:' ? 'wss:' : 'ws:';\n // tRPC WS endpoint is at the root (default upgrade path, not /ws/health)\n const wsUrl = `${wsProtocol}//${httpUrl.host}`;\n\n try {\n this.ws = new WebSocket(wsUrl, {\n headers: { 'Authorization': `Bearer ${this.passphrase}` },\n });\n } catch {\n this.scheduleReconnect();\n return;\n }\n\n this.ws.on('open', () => {\n console.log(`[agent-ws-bridge] ✅ Connected to agent ${this.agentId}`);\n this.currentReconnectDelay = WS_RECONNECT_MIN;\n this.connectionErrorLogged = false;\n this.startHeartbeat();\n this.subscribeAll();\n console.log(`[agent-ws-bridge] Created ${SUBSCRIPTION_PATHS.length} subscriptions for agent ${this.agentId}`);\n });\n\n this.ws.on('message', (raw: Buffer | string) => {\n try {\n const msg: TRPCResponse = JSON.parse(\n typeof raw === 'string' ? raw : raw.toString(),\n );\n this.handleMessage(msg);\n } catch {\n // Ignore unparseable messages\n }\n });\n\n this.ws.on('pong', () => {\n if (this.pongTimeout) { clearTimeout(this.pongTimeout); this.pongTimeout = null; }\n });\n\n this.ws.on('close', () => {\n if (!this.connectionErrorLogged) {\n console.log(`[agent-ws-bridge] ❌ Disconnected from agent ${this.agentId}`);\n }\n this.stopHeartbeat();\n this.ws = null;\n this.subscriptions.clear();\n if (!this.closed) {\n if (!this.connectionErrorLogged) {\n console.log(`[agent-ws-bridge] Scheduling reconnect for agent ${this.agentId} in ${this.currentReconnectDelay}ms`);\n }\n this.scheduleReconnect();\n }\n });\n\n this.ws.on('error', (err) => {\n if (!this.connectionErrorLogged) {\n console.debug('[agent-ws-bridge] WS error for agent', this.agentId + ':', err.message);\n this.connectionErrorLogged = true;\n }\n // 'error' is always followed by 'close'\n });\n }\n\n /**\n * Subscribe to a dynamic tRPC path with an input payload.\n * Returns an unsubscribe function that cleans up on the agent side.\n * Used for per-session subscriptions (e.g. terminal.onData).\n */\n subscribeWithInput(\n path: string,\n input: unknown,\n onData: (data: unknown) => void,\n onStop?: () => void,\n ): () => void {\n const id = this.nextId++;\n this.subscriptions.set(id, { onData, onStop });\n\n if (this.ws?.readyState === WebSocket.OPEN) {\n // Le pendant client de cette ligne vit dans le navigateur : les deux\n // horodatages posés côte à côte disent qui, du client ou du serveur, tarde.\n if (process.env['RUNEYA_DEBUG']) {\n console.log(`[agent-ws-bridge] → souscription ${path} demandée pour l'agent ${this.agentId}`);\n }\n const msg: TRPCRequest = { id, method: 'subscription', params: { path, input } };\n this.ws.send(JSON.stringify(msg));\n } else {\n // Enregistrée mais jamais envoyée : l'appelant attendra des données que\n // l'agent ne sait pas devoir produire. Muet jusqu'ici — c'est le genre\n // d'attente qu'on ne s'explique qu'en relisant ce fichier.\n console.warn(\n `[agent-ws-bridge] ⚠ souscription ${path} enregistrée SANS être envoyée `\n + `(WS non ouvert, état ${this.ws?.readyState ?? 'absent'}) — agent ${this.agentId}`,\n );\n }\n\n return () => {\n this.subscriptions.delete(id);\n if (this.ws?.readyState === WebSocket.OPEN) {\n // tRPC v11 stop message\n this.ws.send(JSON.stringify({ id, method: 'subscription.stop' }));\n }\n };\n }\n\n disconnect(): void {\n this.closed = true;\n this.cleanup();\n }\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n this.pingInterval = setInterval(() => {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\n this.pongTimeout = setTimeout(() => {\n console.log(`[agent-ws-bridge] Pong timeout for agent ${this.agentId}, closing connection`);\n this.ws?.terminate();\n }, PONG_TIMEOUT_MS);\n this.ws.ping();\n }, PING_INTERVAL_MS);\n }\n\n private stopHeartbeat(): void {\n if (this.pingInterval) { clearInterval(this.pingInterval); this.pingInterval = null; }\n if (this.pongTimeout) { clearTimeout(this.pongTimeout); this.pongTimeout = null; }\n }\n\n private cleanup(): void {\n this.stopHeartbeat();\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.ws) {\n // Remove all user listeners, but keep a no-op error handler\n // to prevent unhandled error events when closing in CONNECTING state\n this.ws.removeAllListeners();\n this.ws.on('error', () => {});\n this.ws.close();\n this.ws = null;\n }\n this.subscriptions.clear();\n }\n\n private subscribeAll(): void {\n for (const { path, eventType } of SUBSCRIPTION_PATHS) {\n this.sendSubscription(path, eventType);\n }\n }\n\n private sendSubscription(path: string, eventType: string): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\n\n const id = this.nextId++;\n this.subscriptions.set(id, { eventType });\n\n const msg: TRPCRequest = {\n id,\n method: 'subscription',\n params: { path },\n };\n\n this.ws.send(JSON.stringify(msg));\n }\n\n private handleMessage(msg: TRPCResponse): void {\n const sub = this.subscriptions.get(msg.id);\n if (!sub) return;\n\n if (msg.result?.type === 'data' && msg.result.data !== undefined) {\n const data = msg.result.data;\n\n if (sub.onData) {\n // Dynamic subscription — forward raw data to callback\n sub.onData(data);\n } else if (sub.eventType) {\n if (typeof data !== 'object' || data === null) return;\n // Log batches arrive as arrays — emit with agentId attached for filtering\n if (Array.isArray(data)) {\n this.emit(sub.eventType, { batch: data, agentId: this.agentId });\n } else {\n // Spread agent data first, then override agentId with the trusted value\n // to prevent a compromised agent from spoofing its identity\n this.emit(sub.eventType, {\n ...(data as Record<string, unknown>),\n agentId: this.agentId,\n });\n }\n }\n } else if (msg.result?.type === 'started') {\n const label = sub.eventType ?? '(dynamic)';\n console.log(`[agent-ws-bridge] ✅ Subscription ${label} started for agent ${this.agentId}`);\n } else if (msg.result?.type === 'stopped') {\n const label = sub.eventType ?? '(dynamic)';\n console.log(`[agent-ws-bridge] ⏹️ Subscription ${label} stopped for agent ${this.agentId}`);\n if (sub.onStop) sub.onStop();\n this.subscriptions.delete(msg.id);\n } else if (msg.error) {\n const label = sub.eventType ?? '(dynamic)';\n console.debug('[agent-ws-bridge] Subscription error for', label, 'on agent', this.agentId + ':', msg.error);\n if (sub.onStop) sub.onStop();\n this.subscriptions.delete(msg.id);\n }\n }\n\n private scheduleReconnect(): void {\n if (this.closed || this.reconnectTimer) return;\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n if (!this.closed) {\n this.connect();\n }\n }, this.currentReconnectDelay);\n\n this.currentReconnectDelay = Math.min(this.currentReconnectDelay * 2, WS_RECONNECT_MAX);\n }\n}\n"],"mappings":"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","names":["EventEmitter","WebSocket","z","spawn","dirname","join","mkdir","readFile","writeFile","join","join","mkdir","writeFile","join","readFile","env","join","dirname","spawn","z","EventEmitter","conn","WebSocket","spawn"]}
|
|
@@ -236,7 +236,6 @@ var NOTIFICATION_CATEGORIES = [
|
|
|
236
236
|
"scenario_waiting_input",
|
|
237
237
|
"ai_conversation_complete",
|
|
238
238
|
"ai_conversation_error",
|
|
239
|
-
"service_error_log",
|
|
240
239
|
"service_status_changed",
|
|
241
240
|
"service_health_changed"
|
|
242
241
|
];
|
|
@@ -244,11 +243,8 @@ var NotificationPreferencesSchema = z5.object({
|
|
|
244
243
|
scenario_waiting_input: z5.boolean().default(true),
|
|
245
244
|
ai_conversation_complete: z5.boolean().default(true),
|
|
246
245
|
ai_conversation_error: z5.boolean().default(true),
|
|
247
|
-
service_error_log: z5.boolean().default(true),
|
|
248
246
|
service_status_changed: z5.boolean().default(true),
|
|
249
247
|
service_health_changed: z5.boolean().default(true),
|
|
250
|
-
/** Service IDs excluded from error-log notifications */
|
|
251
|
-
service_error_log_excluded: z5.array(z5.string().max(100)).max(500).default([]),
|
|
252
248
|
/** Service IDs excluded from status-change notifications */
|
|
253
249
|
service_status_changed_excluded: z5.array(z5.string().max(100)).max(500).default([]),
|
|
254
250
|
/** Service IDs excluded from health-change notifications */
|
|
@@ -276,7 +272,13 @@ var CompanionSettingsSchema = z5.object({
|
|
|
276
272
|
effort: z5.enum(["low", "normal", "high"]).optional()
|
|
277
273
|
});
|
|
278
274
|
var SettingsSchema = z5.object({
|
|
279
|
-
|
|
275
|
+
/**
|
|
276
|
+
* Reste de l'ancien choix de mode, que plus rien ne lit. Toujours écrit : la
|
|
277
|
+
* racine machine est partagée par toutes les instances du poste, et une
|
|
278
|
+
* version plus ancienne qui ne le trouverait pas réécrirait ces réglages
|
|
279
|
+
* avec leurs valeurs par défaut.
|
|
280
|
+
*/
|
|
281
|
+
mode: z5.enum(["local-simple", "cloud"]).default("local-simple"),
|
|
280
282
|
cloudUrl: z5.string().url().max(2048).nullable().optional(),
|
|
281
283
|
logParsers: z5.array(JSLogParserSchema).max(100).default([]),
|
|
282
284
|
scenarios: z5.array(ScenarioSchema).max(100).default([]),
|
|
@@ -308,10 +310,8 @@ var SettingsPrivateSchema = z5.object({
|
|
|
308
310
|
scenario_waiting_input: true,
|
|
309
311
|
ai_conversation_complete: true,
|
|
310
312
|
ai_conversation_error: true,
|
|
311
|
-
service_error_log: true,
|
|
312
313
|
service_status_changed: true,
|
|
313
314
|
service_health_changed: true,
|
|
314
|
-
service_error_log_excluded: [],
|
|
315
315
|
service_status_changed_excluded: [],
|
|
316
316
|
service_health_changed_excluded: []
|
|
317
317
|
})),
|
|
@@ -2322,4 +2322,4 @@ export {
|
|
|
2322
2322
|
serviceLinks,
|
|
2323
2323
|
primaryServiceLink
|
|
2324
2324
|
};
|
|
2325
|
-
//# sourceMappingURL=chunk-
|
|
2325
|
+
//# sourceMappingURL=chunk-RVZATF7R.js.map
|