@runeya/runeya 2.0.121 → 2.0.123
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-manager-4HBMLHXU.js → agent-manager-2G55ZFCL.js} +3 -3
- package/{chat-VL6BIVO5.js → chat-MOAVENBI.js} +6 -6
- package/{chunk-HJUFIZ7H.js → chunk-DUHCY3JU.js} +128 -158
- package/chunk-DUHCY3JU.js.map +1 -0
- package/chunk-HK5QNU6P.js +117 -0
- package/chunk-HK5QNU6P.js.map +1 -0
- package/{chunk-CNTL6NVU.js → chunk-MSLFR2EN.js} +118 -58
- package/chunk-MSLFR2EN.js.map +1 -0
- package/{chunk-3N55WR7F.js → chunk-N3ZI35SC.js} +7 -16
- package/chunk-N3ZI35SC.js.map +1 -0
- package/{chunk-OVV52N5F.js → chunk-TNJFDWJX.js} +78 -74
- package/chunk-TNJFDWJX.js.map +1 -0
- package/{companion-AUMA6RGX.js → companion-7VOV4W3T.js} +4 -4
- package/index.js +3 -3
- package/package.json +1 -1
- package/{settings-manager-ROWQ5GC2.js → settings-manager-MAVD4GAS.js} +3 -3
- package/{src-JIS3X4OU.js → src-DN2IGASI.js} +297 -110
- package/src-DN2IGASI.js.map +1 -0
- package/chunk-3N55WR7F.js.map +0 -1
- package/chunk-CNTL6NVU.js.map +0 -1
- package/chunk-HJUFIZ7H.js.map +0 -1
- package/chunk-OVV52N5F.js.map +0 -1
- package/chunk-YZU5FNXR.js +0 -43
- package/chunk-YZU5FNXR.js.map +0 -1
- package/src-JIS3X4OU.js.map +0 -1
- /package/{agent-manager-4HBMLHXU.js.map → agent-manager-2G55ZFCL.js.map} +0 -0
- /package/{chat-VL6BIVO5.js.map → chat-MOAVENBI.js.map} +0 -0
- /package/{companion-AUMA6RGX.js.map → companion-7VOV4W3T.js.map} +0 -0
- /package/{settings-manager-ROWQ5GC2.js.map → settings-manager-MAVD4GAS.js.map} +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
machineRoot
|
|
3
|
+
} from "./chunk-ZFELQG4B.js";
|
|
4
|
+
|
|
5
|
+
// ../server/src/services/disk-freshness.ts
|
|
6
|
+
import { stat } from "fs/promises";
|
|
7
|
+
var seen = /* @__PURE__ */ new Map();
|
|
8
|
+
async function rememberFile(path) {
|
|
9
|
+
try {
|
|
10
|
+
const s = await stat(path);
|
|
11
|
+
seen.set(path, { mtimeMs: s.mtimeMs, size: s.size, ino: s.ino });
|
|
12
|
+
} catch {
|
|
13
|
+
seen.delete(path);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function hasChangedSinceRead(path) {
|
|
17
|
+
const known = seen.get(path);
|
|
18
|
+
if (!known) return true;
|
|
19
|
+
try {
|
|
20
|
+
const s = await stat(path);
|
|
21
|
+
return s.mtimeMs !== known.mtimeMs || s.size !== known.size || s.ino !== known.ino;
|
|
22
|
+
} catch {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function wasRead(path) {
|
|
27
|
+
return seen.has(path);
|
|
28
|
+
}
|
|
29
|
+
async function fingerprintFiles(paths) {
|
|
30
|
+
const stamps = await Promise.all(paths.map(async (path) => {
|
|
31
|
+
try {
|
|
32
|
+
const s = await stat(path);
|
|
33
|
+
return `${path}:${s.mtimeMs}:${s.size}:${s.ino}`;
|
|
34
|
+
} catch {
|
|
35
|
+
return `${path}:-`;
|
|
36
|
+
}
|
|
37
|
+
}));
|
|
38
|
+
return stamps.sort().join("|");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ../server/src/services/run-store-utils.ts
|
|
42
|
+
import { randomUUID } from "crypto";
|
|
43
|
+
import { chmod, mkdir, readdir, rename, rm, stat as stat2, writeFile, readFile } from "fs/promises";
|
|
44
|
+
import { dirname, join, resolve, sep } from "path";
|
|
45
|
+
var SAFE_PATH_SEGMENT_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;
|
|
46
|
+
function resolveConversationRunDir(rootDirName, conversationId) {
|
|
47
|
+
if (!SAFE_PATH_SEGMENT_RE.test(conversationId)) {
|
|
48
|
+
throw new Error("Invalid conversationId");
|
|
49
|
+
}
|
|
50
|
+
const base = resolve(machineRoot(), rootDirName);
|
|
51
|
+
const dir = resolve(base, conversationId);
|
|
52
|
+
if (!dir.startsWith(base + sep)) {
|
|
53
|
+
throw new Error("Invalid conversationId");
|
|
54
|
+
}
|
|
55
|
+
return dir;
|
|
56
|
+
}
|
|
57
|
+
async function atomicWriteSecure(filePath, content) {
|
|
58
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
59
|
+
const tmpPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
60
|
+
try {
|
|
61
|
+
await writeFile(tmpPath, content, typeof content === "string" ? "utf-8" : void 0);
|
|
62
|
+
await rename(tmpPath, filePath);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
await rm(tmpPath, { force: true }).catch(() => {
|
|
65
|
+
});
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
await chmod(filePath, 384);
|
|
69
|
+
}
|
|
70
|
+
var OWN_TEMP_RE = /\.\d+\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.tmp$/;
|
|
71
|
+
var STALE_TEMP_MS = 36e5;
|
|
72
|
+
async function sweepStaleTempFiles(dirs) {
|
|
73
|
+
let removed = 0;
|
|
74
|
+
const limit = Date.now() - STALE_TEMP_MS;
|
|
75
|
+
for (const dir of dirs) {
|
|
76
|
+
let names;
|
|
77
|
+
try {
|
|
78
|
+
names = await readdir(dir);
|
|
79
|
+
} catch {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
for (const name of names.filter((n) => OWN_TEMP_RE.test(n))) {
|
|
83
|
+
const path = join(dir, name);
|
|
84
|
+
try {
|
|
85
|
+
if ((await stat2(path)).mtimeMs > limit) continue;
|
|
86
|
+
await rm(path, { force: true });
|
|
87
|
+
removed += 1;
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return removed;
|
|
93
|
+
}
|
|
94
|
+
async function atomicWriteJsonSecure(filePath, data, pretty = false) {
|
|
95
|
+
await atomicWriteSecure(filePath, JSON.stringify(data, null, pretty ? 2 : 0));
|
|
96
|
+
}
|
|
97
|
+
async function readJsonFileSafe(filePath) {
|
|
98
|
+
try {
|
|
99
|
+
const raw = await readFile(filePath, "utf-8");
|
|
100
|
+
return JSON.parse(raw);
|
|
101
|
+
} catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export {
|
|
107
|
+
rememberFile,
|
|
108
|
+
hasChangedSinceRead,
|
|
109
|
+
wasRead,
|
|
110
|
+
fingerprintFiles,
|
|
111
|
+
resolveConversationRunDir,
|
|
112
|
+
atomicWriteSecure,
|
|
113
|
+
sweepStaleTempFiles,
|
|
114
|
+
atomicWriteJsonSecure,
|
|
115
|
+
readJsonFileSafe
|
|
116
|
+
};
|
|
117
|
+
//# sourceMappingURL=chunk-HK5QNU6P.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../server/src/services/disk-freshness.ts","../../server/src/services/run-store-utils.ts"],"sourcesContent":["import { stat } from 'node:fs/promises';\n\n/**\n * Le fichier a-t-il changé depuis qu'on l'a lu ?\n *\n * Les stores gardent de côté ce qu'ils n'ont pas su déchiffrer, pour le\n * réécrire tel quel plutôt que de l'effacer. Cette copie est la bonne réponse\n * tant que personne d'autre ne touche au fichier — et la mauvaise dès que\n * quelqu'un le répare par-dessous : la copie, elle, ne se périme jamais, et la\n * sauvegarde suivante réinstalle l'état d'avant la réparation.\n *\n * C'est arrivé deux fois de suite sur le même coffre : une conversion de clé\n * menée sur le disque, défaite quelques minutes plus tard par un store qui\n * tenait encore l'ancien chiffré en mémoire.\n *\n * D'où ce repère : l'empreinte du fichier au moment de la lecture. Si elle a\n * bougé, la copie mémoire ne fait plus foi et doit être abandonnée.\n */\n\ninterface Fingerprint {\n mtimeMs: number;\n size: number;\n /**\n * L'inode du chemin, qui change à chaque `rename` par-dessus.\n *\n * La date et la taille laissent passer le motif d'écriture de tout le dépôt :\n * les stores réécrivent le fichier entier, donc une modification rend souvent\n * le même nombre d'octets, et deux instances qui réagissent au même événement\n * écrivent dans la même milliseconde. Le fichier temporaire renommé\n * par-dessus, lui, installe toujours un autre inode.\n *\n * Là où le système n'en expose pas (il rend alors 0), le repère retombe sur\n * la date et la taille — ce qu'il valait avant.\n */\n ino: number;\n}\n\nconst seen = new Map<string, Fingerprint>();\n\n/** Retenir l'état d'un fichier au moment où on vient de le lire. */\nexport async function rememberFile(path: string): Promise<void> {\n try {\n const s = await stat(path);\n seen.set(path, { mtimeMs: s.mtimeMs, size: s.size, ino: s.ino });\n } catch {\n // Pas encore de fichier : rien à retenir, et rien à réémettre non plus.\n seen.delete(path);\n }\n}\n\n/**\n * Vrai si le fichier a changé — ou si l'on ne sait pas.\n *\n * L'ignorance compte comme un changement : réécrire une copie dont on ne peut\n * plus prouver qu'elle correspond au disque est précisément le geste à éviter.\n */\nexport async function hasChangedSinceRead(path: string): Promise<boolean> {\n const known = seen.get(path);\n if (!known) return true;\n try {\n const s = await stat(path);\n return s.mtimeMs !== known.mtimeMs || s.size !== known.size || s.ino !== known.ino;\n } catch {\n return true;\n }\n}\n\n/**\n * A-t-on lu ce fichier ?\n *\n * `hasChangedSinceRead` tient l'ignorance pour un changement : c'est juste\n * quand il s'agit de décider si une copie mémoire fait encore foi. Ça ne l'est\n * pas pour décider d'écrire — un fichier jamais lu est le cas d'un coffre neuf,\n * et le traiter comme « modifié sous nos pieds » revenait à ne jamais écrire la\n * première fois. Les appelants qui protègent une écriture demandent donc les\n * deux : connu, et inchangé.\n */\nexport function wasRead(path: string): boolean {\n return seen.has(path);\n}\n\n/** Oublier un fichier — après un rechargement complet, par exemple. */\nexport function forgetFile(path: string): void {\n seen.delete(path);\n}\n\n/**\n * L'empreinte d'un ensemble de fichiers : lesquels, et dans quel état.\n *\n * Le repère ci-dessus répond pour un fichier qu'on a lu soi-même ; celui-ci\n * répond pour un store dont le contenu vient de plusieurs fichiers à la fois —\n * un par coffre — et dont la question est « le disque a-t-il bougé depuis mon\n * dernier chargement ? ». Comparer deux empreintes coûte quelques `stat` ;\n * recharger coûte autant de lectures, d'analyses et de déchiffrements.\n *\n * L'absence d'un fichier en fait partie : un coffre disparu change l'empreinte\n * autant qu'un coffre modifié.\n */\nexport async function fingerprintFiles(paths: string[]): Promise<string> {\n const stamps = await Promise.all(paths.map(async (path) => {\n try {\n const s = await stat(path);\n // L'inode en fait partie pour la raison décrite sur `Fingerprint` : un\n // fichier renommé par-dessus la cible peut n'avoir bougé ni en taille ni\n // en date, et reste pourtant un autre contenu.\n return `${path}:${s.mtimeMs}:${s.size}:${s.ino}`;\n } catch {\n return `${path}:-`;\n }\n }));\n return stamps.sort().join('|');\n}\n","import { randomUUID } from 'node:crypto';\nimport { chmod, mkdir, readdir, rename, rm, stat, writeFile, readFile } from 'node:fs/promises';\nimport { dirname, join, resolve, sep } from 'node:path';\nimport { machineRoot } from './vault-resolver.js';\n\nconst SAFE_PATH_SEGMENT_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;\n\nexport function resolveConversationRunDir(rootDirName: string, conversationId: string): string {\n if (!SAFE_PATH_SEGMENT_RE.test(conversationId)) {\n throw new Error('Invalid conversationId');\n }\n // Machine-wide, like the conversations these runs belong to: using the launch\n // directory would put a `.runeya` in a repository just for asking the AI a\n // question about a cloud project.\n const base = resolve(machineRoot(), rootDirName);\n const dir = resolve(base, conversationId);\n if (!dir.startsWith(base + sep)) {\n throw new Error('Invalid conversationId');\n }\n return dir;\n}\n\nexport async function atomicWriteSecure(filePath: string, content: string | Uint8Array): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n // Un nom par écriture : deux Runeya d'un même poste écrivent les mêmes\n // fichiers, et un `.tmp` commun se fait renommer sous le nez de l'autre.\n const tmpPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;\n try {\n await writeFile(tmpPath, content, typeof content === 'string' ? 'utf-8' : undefined);\n await rename(tmpPath, filePath);\n } catch (err) {\n // Un nom unique ne se réécrit jamais : laissé là, chaque échec en ajouterait un.\n await rm(tmpPath, { force: true }).catch(() => {});\n throw err;\n }\n await chmod(filePath, 0o600);\n}\n\n/** Le nom qu'`atomicWriteSecure` donne à ses fichiers temporaires : `<fichier>.<pid>.<uuid>.tmp`. */\nconst OWN_TEMP_RE = /\\.\\d+\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\.tmp$/;\nconst STALE_TEMP_MS = 3600_000;\n\n/**\n * Retirer les fichiers temporaires qu'un arrêt brutal a laissés.\n *\n * Un nom par écriture ne se réécrit jamais : chaque arrêt en pleine écriture en\n * laissait un pour toujours. Seuls les nôtres sont concernés, et seulement\n * passé une heure — un plus récent peut appartenir à une écriture en cours,\n * celle d'une autre Runeya du même poste.\n */\nexport async function sweepStaleTempFiles(dirs: string[]): Promise<number> {\n let removed = 0;\n const limit = Date.now() - STALE_TEMP_MS;\n for (const dir of dirs) {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n continue;\n }\n for (const name of names.filter((n) => OWN_TEMP_RE.test(n))) {\n const path = join(dir, name);\n try {\n if ((await stat(path)).mtimeMs > limit) continue;\n await rm(path, { force: true });\n removed += 1;\n } catch {\n // Disparu entre-temps : rien à retirer.\n }\n }\n }\n return removed;\n}\n\nexport async function atomicWriteJsonSecure(filePath: string, data: unknown, pretty = false): Promise<void> {\n await atomicWriteSecure(filePath, JSON.stringify(data, null, pretty ? 2 : 0));\n}\n\nexport async function readJsonFileSafe<T>(filePath: string): Promise<T | null> {\n try {\n const raw = await readFile(filePath, 'utf-8');\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":["stat","stat"]}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
+
atomicWriteSecure,
|
|
2
3
|
fingerprintFiles,
|
|
3
4
|
hasChangedSinceRead,
|
|
4
5
|
rememberFile,
|
|
5
6
|
wasRead
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-HK5QNU6P.js";
|
|
7
8
|
import {
|
|
8
9
|
AGENT_FETCH_TIMEOUT,
|
|
9
10
|
AGENT_HEALTH_POLL_INTERVAL,
|
|
@@ -390,7 +391,7 @@ var agentStore = new AgentStore();
|
|
|
390
391
|
|
|
391
392
|
// ../server/src/services/service-store.ts
|
|
392
393
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
393
|
-
import { readFile as readFile3,
|
|
394
|
+
import { readFile as readFile3, rm as rm2 } from "fs/promises";
|
|
394
395
|
import { join as join3 } from "path";
|
|
395
396
|
|
|
396
397
|
// ../server/src/services/project-key.ts
|
|
@@ -501,9 +502,49 @@ function isVaultConflicted(root) {
|
|
|
501
502
|
return conflicted.has(root);
|
|
502
503
|
}
|
|
503
504
|
|
|
505
|
+
// ../server/src/services/store-disk-coordinator.ts
|
|
506
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
507
|
+
var insideReload = new AsyncLocalStorage();
|
|
508
|
+
var StoreDiskCoordinator = class {
|
|
509
|
+
reloading = null;
|
|
510
|
+
saves = 0;
|
|
511
|
+
queue = Promise.resolve();
|
|
512
|
+
/**
|
|
513
|
+
* `read` rend de quoi substituer le nouvel état, ou `null` s'il n'y a rien à
|
|
514
|
+
* relire. La substitution doit être synchrone : c'est ce qui la rend atomique.
|
|
515
|
+
*/
|
|
516
|
+
load(read) {
|
|
517
|
+
const inside = insideReload.getStore();
|
|
518
|
+
if (inside && (inside.has(this) || this.reloading)) return Promise.resolve();
|
|
519
|
+
this.reloading ??= this.reload(read).finally(() => {
|
|
520
|
+
this.reloading = null;
|
|
521
|
+
});
|
|
522
|
+
return this.reloading;
|
|
523
|
+
}
|
|
524
|
+
async reload(read) {
|
|
525
|
+
for (; ; ) {
|
|
526
|
+
await this.queue;
|
|
527
|
+
const savesBefore = this.saves;
|
|
528
|
+
const outer = insideReload.getStore() ?? /* @__PURE__ */ new Set();
|
|
529
|
+
const commit = await insideReload.run(/* @__PURE__ */ new Set([...outer, this]), read);
|
|
530
|
+
if (!commit) return;
|
|
531
|
+
if (this.saves !== savesBefore) continue;
|
|
532
|
+
commit();
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
save(write) {
|
|
537
|
+
this.saves += 1;
|
|
538
|
+
const run = this.queue.then(write);
|
|
539
|
+
this.queue = run.catch(() => {
|
|
540
|
+
});
|
|
541
|
+
return run;
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
|
|
504
545
|
// ../server/src/services/environment-store.ts
|
|
505
546
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
506
|
-
import { readFile as readFile2,
|
|
547
|
+
import { readFile as readFile2, rm } from "fs/promises";
|
|
507
548
|
import { join as join2 } from "path";
|
|
508
549
|
|
|
509
550
|
// ../server/src/services/crypto-helpers.ts
|
|
@@ -647,6 +688,7 @@ var EnvironmentStore = class {
|
|
|
647
688
|
* refusée l'est pour toujours et la modification de l'utilisateur se perd.
|
|
648
689
|
*/
|
|
649
690
|
fingerprint = "";
|
|
691
|
+
disk = new StoreDiskCoordinator();
|
|
650
692
|
listeners = /* @__PURE__ */ new Set();
|
|
651
693
|
/** Notified after every persisted mutation, for the cloud sync bridge. */
|
|
652
694
|
onChange(fn) {
|
|
@@ -810,7 +852,7 @@ var EnvironmentStore = class {
|
|
|
810
852
|
}
|
|
811
853
|
return result;
|
|
812
854
|
}
|
|
813
|
-
decryptEnvironment(environment, vault) {
|
|
855
|
+
decryptEnvironment(environment, vault, unreadable = this.unreadableSecrets) {
|
|
814
856
|
let decryptedVars = environment.variables;
|
|
815
857
|
if (environment.variables && Object.keys(environment.variables).length > 0) {
|
|
816
858
|
const failed = /* @__PURE__ */ new Set();
|
|
@@ -820,8 +862,8 @@ var EnvironmentStore = class {
|
|
|
820
862
|
(variableId) => failed.add(variableId),
|
|
821
863
|
vault ?? this.vaultOf(environment)
|
|
822
864
|
);
|
|
823
|
-
if (failed.size > 0)
|
|
824
|
-
else
|
|
865
|
+
if (failed.size > 0) unreadable.set(environment.id, failed);
|
|
866
|
+
else unreadable.delete(environment.id);
|
|
825
867
|
}
|
|
826
868
|
return { ...environment, variables: decryptedVars };
|
|
827
869
|
}
|
|
@@ -837,39 +879,47 @@ var EnvironmentStore = class {
|
|
|
837
879
|
* store reads environments while absorbing a project, which would deadlock).
|
|
838
880
|
*/
|
|
839
881
|
async load() {
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
882
|
+
await this.disk.load(async () => {
|
|
883
|
+
const fingerprint = await this.diskFingerprint();
|
|
884
|
+
if (this.loaded && fingerprint === this.fingerprint) return null;
|
|
885
|
+
await ensureProjectsLoaded();
|
|
886
|
+
if (!getEncryptionKey()) return null;
|
|
887
|
+
const next = await this.readVaults();
|
|
888
|
+
return () => {
|
|
889
|
+
this.environments = next.environments;
|
|
890
|
+
this.diskEncryptedSlots = next.diskEncryptedSlots;
|
|
891
|
+
this.roots = next.roots;
|
|
892
|
+
this.unreadableSecrets = next.unreadableSecrets;
|
|
893
|
+
this.fingerprint = fingerprint;
|
|
894
|
+
this.loaded = true;
|
|
895
|
+
};
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
/** Lire tous les coffres dans des tables neuves, que `load` substitue d'un coup. */
|
|
899
|
+
async readVaults() {
|
|
900
|
+
const environments = /* @__PURE__ */ new Map();
|
|
901
|
+
const diskEncryptedSlots = /* @__PURE__ */ new Map();
|
|
902
|
+
const roots = /* @__PURE__ */ new Map();
|
|
903
|
+
const unreadableSecrets = /* @__PURE__ */ new Map();
|
|
904
|
+
for (const root of await allVaultRoots()) {
|
|
855
905
|
try {
|
|
856
906
|
const raw = await readFile2(this.getFilePath(root), "utf-8");
|
|
857
907
|
await rememberFile(this.getFilePath(root));
|
|
858
908
|
const data = JSON.parse(raw);
|
|
859
|
-
const clash = data.find((e) =>
|
|
909
|
+
const clash = data.find((e) => environments.has(e.id) && roots.get(e.id) !== root);
|
|
860
910
|
if (clash) {
|
|
861
911
|
markVaultConflict(root, `environnement ${clash.id}`);
|
|
862
912
|
continue;
|
|
863
913
|
}
|
|
864
914
|
for (const e of data) {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
this.environments.set(e.id, decrypted);
|
|
915
|
+
diskEncryptedSlots.set(e.id, this.extractEncryptedCache(e));
|
|
916
|
+
roots.set(e.id, root);
|
|
917
|
+
environments.set(e.id, this.decryptEnvironment(e, vaultIdOfRoot(root), unreadableSecrets));
|
|
869
918
|
}
|
|
870
919
|
} catch {
|
|
871
920
|
}
|
|
872
921
|
}
|
|
922
|
+
return { environments, diskEncryptedSlots, roots, unreadableSecrets };
|
|
873
923
|
}
|
|
874
924
|
/**
|
|
875
925
|
* Write each vault that holds — or held — an environment.
|
|
@@ -891,7 +941,10 @@ var EnvironmentStore = class {
|
|
|
891
941
|
if (wasRead(path) && await hasChangedSinceRead(path)) this.staleRoots.add(root);
|
|
892
942
|
}
|
|
893
943
|
}
|
|
894
|
-
|
|
944
|
+
save() {
|
|
945
|
+
return this.disk.save(() => this.writeVaults());
|
|
946
|
+
}
|
|
947
|
+
async writeVaults() {
|
|
895
948
|
await this.markStaleRoots();
|
|
896
949
|
const byRoot = /* @__PURE__ */ new Map();
|
|
897
950
|
for (const root of this.roots.values()) {
|
|
@@ -921,11 +974,7 @@ var EnvironmentStore = class {
|
|
|
921
974
|
});
|
|
922
975
|
continue;
|
|
923
976
|
}
|
|
924
|
-
await
|
|
925
|
-
const tmpPath = filePath + ".tmp";
|
|
926
|
-
await writeFile2(tmpPath, JSON.stringify(environments, null, 2), "utf-8");
|
|
927
|
-
await rename2(tmpPath, filePath);
|
|
928
|
-
await chmod2(filePath, 384);
|
|
977
|
+
await atomicWriteSecure(filePath, JSON.stringify(environments, null, 2));
|
|
929
978
|
await rememberFile(filePath);
|
|
930
979
|
}
|
|
931
980
|
this.fingerprint = await this.diskFingerprint();
|
|
@@ -1273,6 +1322,7 @@ var ServiceStore = class {
|
|
|
1273
1322
|
* pour toutes ne verrait jamais les services créés par l'autre.
|
|
1274
1323
|
*/
|
|
1275
1324
|
fingerprint = "";
|
|
1325
|
+
disk = new StoreDiskCoordinator();
|
|
1276
1326
|
listeners = /* @__PURE__ */ new Set();
|
|
1277
1327
|
/**
|
|
1278
1328
|
* Last on-disk (encrypted) form per service id, so `prepareForDisk` can reuse
|
|
@@ -1344,20 +1394,30 @@ var ServiceStore = class {
|
|
|
1344
1394
|
*/
|
|
1345
1395
|
async load() {
|
|
1346
1396
|
if (!getEncryptionKey()) return;
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1397
|
+
await this.disk.load(async () => {
|
|
1398
|
+
const fingerprint = await this.diskFingerprint();
|
|
1399
|
+
if (this.loaded && fingerprint === this.fingerprint) return null;
|
|
1400
|
+
await ensureProjectsLoaded();
|
|
1401
|
+
const next = await this.readVaults();
|
|
1402
|
+
return () => {
|
|
1403
|
+
this.services = next.services;
|
|
1404
|
+
this.diskForm = next.diskForm;
|
|
1405
|
+
this.roots = next.roots;
|
|
1406
|
+
this.fingerprint = fingerprint;
|
|
1407
|
+
this.loaded = true;
|
|
1408
|
+
if (this.services.size > 0) this.notify();
|
|
1409
|
+
};
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
async readVaults() {
|
|
1413
|
+
const services = /* @__PURE__ */ new Map();
|
|
1414
|
+
const diskForm = /* @__PURE__ */ new Map();
|
|
1415
|
+
const roots = /* @__PURE__ */ new Map();
|
|
1416
|
+
for (const root of await allVaultRoots()) {
|
|
1357
1417
|
try {
|
|
1358
1418
|
const raw = await readFile3(this.getFilePath(root), "utf-8");
|
|
1359
1419
|
const data = JSON.parse(raw);
|
|
1360
|
-
const clash = data.find((s) =>
|
|
1420
|
+
const clash = data.find((s) => services.has(s.id) && roots.get(s.id) !== root);
|
|
1361
1421
|
if (clash) {
|
|
1362
1422
|
markVaultConflict(root, `service ${clash.id}`);
|
|
1363
1423
|
continue;
|
|
@@ -1368,17 +1428,17 @@ var ServiceStore = class {
|
|
|
1368
1428
|
if (cmd.shell === void 0) cmd.shell = true;
|
|
1369
1429
|
}
|
|
1370
1430
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1431
|
+
diskForm.set(s.id, s);
|
|
1432
|
+
services.set(s.id, {
|
|
1373
1433
|
...s,
|
|
1374
1434
|
logSources: decryptLogSources(s.logSources ?? [], vaultIdOfRoot(root))
|
|
1375
1435
|
});
|
|
1376
|
-
|
|
1436
|
+
roots.set(s.id, root);
|
|
1377
1437
|
}
|
|
1378
1438
|
} catch {
|
|
1379
1439
|
}
|
|
1380
1440
|
}
|
|
1381
|
-
|
|
1441
|
+
return { services, diskForm, roots };
|
|
1382
1442
|
}
|
|
1383
1443
|
/**
|
|
1384
1444
|
* Write each vault that holds — or held — a service.
|
|
@@ -1387,7 +1447,10 @@ var ServiceStore = class {
|
|
|
1387
1447
|
* another vault must not survive in the one it left, and a `[]` left behind
|
|
1388
1448
|
* would keep (or recreate) a `.runeya` in a directory that holds nothing.
|
|
1389
1449
|
*/
|
|
1390
|
-
|
|
1450
|
+
save() {
|
|
1451
|
+
return this.disk.save(() => this.writeVaults());
|
|
1452
|
+
}
|
|
1453
|
+
async writeVaults() {
|
|
1391
1454
|
const byRoot = /* @__PURE__ */ new Map();
|
|
1392
1455
|
for (const root of this.roots.values()) {
|
|
1393
1456
|
if (!isVaultConflicted(root)) byRoot.set(root, []);
|
|
@@ -1410,11 +1473,7 @@ var ServiceStore = class {
|
|
|
1410
1473
|
});
|
|
1411
1474
|
continue;
|
|
1412
1475
|
}
|
|
1413
|
-
await
|
|
1414
|
-
const tmpPath = filePath + ".tmp";
|
|
1415
|
-
await writeFile3(tmpPath, JSON.stringify(services, null, 2), "utf-8");
|
|
1416
|
-
await rename3(tmpPath, filePath);
|
|
1417
|
-
await chmod3(filePath, 384);
|
|
1476
|
+
await atomicWriteSecure(filePath, JSON.stringify(services, null, 2));
|
|
1418
1477
|
}
|
|
1419
1478
|
this.fingerprint = await this.diskFingerprint();
|
|
1420
1479
|
}
|
|
@@ -1627,7 +1686,7 @@ import { dirname as dirname2, join as join5 } from "path";
|
|
|
1627
1686
|
import { fileURLToPath } from "url";
|
|
1628
1687
|
|
|
1629
1688
|
// ../server/src/services/instance-registry.ts
|
|
1630
|
-
import { mkdir as
|
|
1689
|
+
import { mkdir as mkdir2, readdir, readFile as readFile4, writeFile as writeFile2, rm as rm3 } from "fs/promises";
|
|
1631
1690
|
import { readFileSync } from "fs";
|
|
1632
1691
|
import dotenv from "dotenv";
|
|
1633
1692
|
import { spawn } from "child_process";
|
|
@@ -1724,9 +1783,9 @@ function selfRecord() {
|
|
|
1724
1783
|
}
|
|
1725
1784
|
async function registerInstance() {
|
|
1726
1785
|
try {
|
|
1727
|
-
await
|
|
1786
|
+
await mkdir2(registryDir(), { recursive: true });
|
|
1728
1787
|
published = selfRecord();
|
|
1729
|
-
await
|
|
1788
|
+
await writeFile2(fileFor(process.pid), JSON.stringify(published, null, 2));
|
|
1730
1789
|
} catch (err) {
|
|
1731
1790
|
console.log("[instances] Could not register this instance:", err.message);
|
|
1732
1791
|
}
|
|
@@ -1739,7 +1798,7 @@ async function setInstanceAgentPid(agentPid) {
|
|
|
1739
1798
|
if (!published) return;
|
|
1740
1799
|
published = { ...published, agentPid: agentPid ?? void 0 };
|
|
1741
1800
|
try {
|
|
1742
|
-
await
|
|
1801
|
+
await writeFile2(fileFor(process.pid), JSON.stringify(published, null, 2));
|
|
1743
1802
|
} catch {
|
|
1744
1803
|
}
|
|
1745
1804
|
}
|
|
@@ -3011,6 +3070,7 @@ export {
|
|
|
3011
3070
|
decryptVariableSlots,
|
|
3012
3071
|
extractEncryptedVariableSlots,
|
|
3013
3072
|
isEncryptedValue,
|
|
3073
|
+
StoreDiskCoordinator,
|
|
3014
3074
|
projectKey,
|
|
3015
3075
|
AmbiguousProjectError,
|
|
3016
3076
|
registerProjectLoader,
|
|
@@ -3035,4 +3095,4 @@ export {
|
|
|
3035
3095
|
traefikManager,
|
|
3036
3096
|
agentManager
|
|
3037
3097
|
};
|
|
3038
|
-
//# sourceMappingURL=chunk-
|
|
3098
|
+
//# sourceMappingURL=chunk-MSLFR2EN.js.map
|