@kubb/studio 5.3.12 → 5.3.13
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/dist/index.cjs +208 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +210 -86
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs +5 -0
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.ts +36 -17
- package/dist/protocol.js +5 -1
- package/dist/protocol.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -74,8 +74,27 @@ const agentDefaults = {
|
|
|
74
74
|
maxHeartbeatIntervalMs: 6e4,
|
|
75
75
|
/** How long a heartbeat ping may take before the session is treated as dead. */
|
|
76
76
|
heartbeatTimeoutMs: 1e4,
|
|
77
|
-
poolSize: 1
|
|
77
|
+
poolSize: 1,
|
|
78
|
+
maxGenerations: 8,
|
|
79
|
+
maxGenerationsMb: 100,
|
|
80
|
+
maxSnapshotMb: 50
|
|
78
81
|
};
|
|
82
|
+
function positiveNumber(value) {
|
|
83
|
+
const parsed = Number(value);
|
|
84
|
+
return value && Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* How many generations an agent keeps and how large they may get, read from
|
|
88
|
+
* `KUBB_AGENT_MAX_GENERATIONS`, `KUBB_AGENT_MAX_GENERATIONS_MB` and `KUBB_AGENT_MAX_SNAPSHOT_MB`.
|
|
89
|
+
* An unset or invalid value keeps the default.
|
|
90
|
+
*/
|
|
91
|
+
function resolveGenerationLimits(env = process.env) {
|
|
92
|
+
return {
|
|
93
|
+
maxCount: Math.max(1, Math.floor(positiveNumber(env.KUBB_AGENT_MAX_GENERATIONS) ?? agentDefaults.maxGenerations)),
|
|
94
|
+
maxMb: positiveNumber(env.KUBB_AGENT_MAX_GENERATIONS_MB) ?? agentDefaults.maxGenerationsMb,
|
|
95
|
+
maxSnapshotMb: positiveNumber(env.KUBB_AGENT_MAX_SNAPSHOT_MB) ?? agentDefaults.maxSnapshotMb
|
|
96
|
+
};
|
|
97
|
+
}
|
|
79
98
|
//#endregion
|
|
80
99
|
//#region ../../internals/utils/src/casing.ts
|
|
81
100
|
/**
|
|
@@ -706,7 +725,7 @@ async function createAgent({ studioUrl, token, name, machineToken }) {
|
|
|
706
725
|
}
|
|
707
726
|
//#endregion
|
|
708
727
|
//#region package.json
|
|
709
|
-
var version = "5.3.
|
|
728
|
+
var version = "5.3.13";
|
|
710
729
|
//#endregion
|
|
711
730
|
//#region src/hooks.ts
|
|
712
731
|
/**
|
|
@@ -1795,6 +1814,116 @@ async function createSnapshotPackage(files, packageInfo) {
|
|
|
1795
1814
|
}
|
|
1796
1815
|
}
|
|
1797
1816
|
//#endregion
|
|
1817
|
+
//#region src/generations.ts
|
|
1818
|
+
const READ_CONCURRENCY = 50;
|
|
1819
|
+
const MB = 1048576;
|
|
1820
|
+
const INDEX_KEY = "studio/generations.json";
|
|
1821
|
+
const hashOf = (content) => (0, node_crypto.createHash)("sha1").update(content).digest("hex").slice(0, 16);
|
|
1822
|
+
/**
|
|
1823
|
+
* What the output directory holds on disk before a run. `undefined` when `outputPath` is not a real
|
|
1824
|
+
* subdirectory of `root` (listing the root would take in every source file) or holds more than `maxFiles`.
|
|
1825
|
+
*/
|
|
1826
|
+
async function listDisk({ root, outputPath, maxFiles }) {
|
|
1827
|
+
const outputDir = (0, node_path.resolve)(root, outputPath);
|
|
1828
|
+
const fromRoot = (0, node_path.relative)((0, node_path.resolve)(root), outputDir);
|
|
1829
|
+
if (!fromRoot || fromRoot.startsWith("..") || fromRoot.startsWith(node_path.sep)) return void 0;
|
|
1830
|
+
const storage = (0, _kubb_core.fsStorage)();
|
|
1831
|
+
const keys = await storage.readKeys(outputDir);
|
|
1832
|
+
if (keys.length > maxFiles) return void 0;
|
|
1833
|
+
const paths = keys.filter((key) => !key.split("/").includes("node_modules")).map((key) => (0, node_path.relative)((0, node_path.resolve)(root), (0, node_path.resolve)(outputDir, key)).replaceAll("\\", "/"));
|
|
1834
|
+
return {
|
|
1835
|
+
storage,
|
|
1836
|
+
root,
|
|
1837
|
+
paths: new Set(paths)
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Keeps recent generations by job id in `storage`, with an index next to them so a store on disk
|
|
1842
|
+
* survives a restart. A generation is only ever looked up by its job id, so on a pooled sandbox one
|
|
1843
|
+
* tenant never reaches another's output. The oldest go past `maxCount` or `maxMb`, but the newest
|
|
1844
|
+
* always stays.
|
|
1845
|
+
*/
|
|
1846
|
+
function createGenerationStore({ storage, maxCount, maxMb }) {
|
|
1847
|
+
let index;
|
|
1848
|
+
const dirOf = (jobId) => `studio/generations/${hashOf(jobId)}/`;
|
|
1849
|
+
async function load() {
|
|
1850
|
+
if (index) return index;
|
|
1851
|
+
const stored = await storage.readItem(INDEX_KEY).catch(() => null);
|
|
1852
|
+
try {
|
|
1853
|
+
index = stored ? JSON.parse(stored) : [];
|
|
1854
|
+
} catch {
|
|
1855
|
+
index = [];
|
|
1856
|
+
}
|
|
1857
|
+
return index;
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* Copies `files` into the store as one set of `jobId`. Above `maxSetMb` only the hashes are kept.
|
|
1861
|
+
*/
|
|
1862
|
+
async function keep({ jobId, source, files, maxSetMb }) {
|
|
1863
|
+
const maxSetBytes = maxSetMb * MB;
|
|
1864
|
+
const hashes = {};
|
|
1865
|
+
let bytes = 0;
|
|
1866
|
+
await inParallel({
|
|
1867
|
+
items: [...files.paths],
|
|
1868
|
+
limit: READ_CONCURRENCY,
|
|
1869
|
+
run: async (path) => {
|
|
1870
|
+
if (path.split("/").includes("..")) return;
|
|
1871
|
+
const content = await files.storage.readItem((0, node_path.resolve)(files.root, path));
|
|
1872
|
+
if (content === null) return;
|
|
1873
|
+
hashes[path] = hashOf(content);
|
|
1874
|
+
bytes += Buffer.byteLength(content);
|
|
1875
|
+
if (bytes <= maxSetBytes) await storage.writeItem(`${dirOf(jobId)}${source}/${path}`, content);
|
|
1876
|
+
}
|
|
1877
|
+
});
|
|
1878
|
+
if (bytes > maxSetBytes) {
|
|
1879
|
+
await storage.empty(`${dirOf(jobId)}${source}/`);
|
|
1880
|
+
return {
|
|
1881
|
+
paths: [],
|
|
1882
|
+
hashes,
|
|
1883
|
+
bytes: 0
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
return {
|
|
1887
|
+
paths: Object.keys(hashes),
|
|
1888
|
+
hashes,
|
|
1889
|
+
bytes
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
async function drop(jobId) {
|
|
1893
|
+
await storage.empty(dirOf(jobId));
|
|
1894
|
+
}
|
|
1895
|
+
return {
|
|
1896
|
+
keep,
|
|
1897
|
+
drop,
|
|
1898
|
+
get: async (jobId) => (await load()).find((generation) => generation.jobId === jobId),
|
|
1899
|
+
latest: async () => (await load()).at(-1),
|
|
1900
|
+
async add(generation) {
|
|
1901
|
+
const entries = (await load()).filter((entry) => entry.jobId !== generation.jobId);
|
|
1902
|
+
entries.push(generation);
|
|
1903
|
+
const weight = () => entries.reduce((sum, { output, disk }) => sum + output.bytes + (disk?.bytes ?? 0), 0);
|
|
1904
|
+
while (entries.length > 1 && (entries.length > maxCount || weight() > maxMb * MB)) await drop(entries.shift().jobId);
|
|
1905
|
+
index = entries;
|
|
1906
|
+
await storage.writeItem(INDEX_KEY, JSON.stringify(entries));
|
|
1907
|
+
},
|
|
1908
|
+
/**
|
|
1909
|
+
* Reads the requested paths the set holds, skipping any it does not.
|
|
1910
|
+
*/
|
|
1911
|
+
async read({ generation, source, paths }) {
|
|
1912
|
+
const kept = new Set(generation[source]?.paths);
|
|
1913
|
+
const files = {};
|
|
1914
|
+
await inParallel({
|
|
1915
|
+
items: paths.filter((path) => kept.has(path)),
|
|
1916
|
+
limit: READ_CONCURRENCY,
|
|
1917
|
+
run: async (path) => {
|
|
1918
|
+
const content = await storage.readItem(`${dirOf(generation.jobId)}${source}/${path}`);
|
|
1919
|
+
if (content !== null) files[path] = content;
|
|
1920
|
+
}
|
|
1921
|
+
});
|
|
1922
|
+
return files;
|
|
1923
|
+
}
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
//#endregion
|
|
1798
1927
|
//#region src/ws.ts
|
|
1799
1928
|
/**
|
|
1800
1929
|
* How long the initial handshake may take before the socket is closed and the reconnect loop
|
|
@@ -1805,12 +1934,6 @@ const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__
|
|
|
1805
1934
|
function relativeStoragePath(root, filePath) {
|
|
1806
1935
|
return ((0, node_path.isAbsolute)(filePath) ? (0, node_path.relative)((0, node_path.resolve)(root), filePath) : filePath).replaceAll("\\", "/");
|
|
1807
1936
|
}
|
|
1808
|
-
/**
|
|
1809
|
-
* Inverse of {@link relativeStoragePath}: rebuilds the storage key a relative path came from.
|
|
1810
|
-
*/
|
|
1811
|
-
function absoluteStoragePath(root, relativePath) {
|
|
1812
|
-
return (0, node_path.resolve)(root, relativePath);
|
|
1813
|
-
}
|
|
1814
1937
|
async function resolvePeerDependencies(names) {
|
|
1815
1938
|
const uniqueNames = [...new Set(names.map(toPackageName))];
|
|
1816
1939
|
const peerDependencies = {};
|
|
@@ -1936,10 +2059,12 @@ function createGenerationStream(hooks, jobId, options = {}) {
|
|
|
1936
2059
|
const { peerDependencies, missingDependencies } = await resolvePeerDependencies(config.plugins.map(({ name }) => name));
|
|
1937
2060
|
const keys = await storage.readKeys();
|
|
1938
2061
|
const paths = new Set(keys.map((key) => relativeStoragePath(config.root, key)));
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
2062
|
+
options.onGenerationEnd?.({
|
|
2063
|
+
output: {
|
|
2064
|
+
storage,
|
|
2065
|
+
root: config.root,
|
|
2066
|
+
paths
|
|
2067
|
+
},
|
|
1943
2068
|
peerDependencies,
|
|
1944
2069
|
missingDependencies
|
|
1945
2070
|
});
|
|
@@ -2090,39 +2215,9 @@ const connectWebSocketRpc = async ({ url, token, local }) => {
|
|
|
2090
2215
|
//#endregion
|
|
2091
2216
|
//#region src/StudioSession.ts
|
|
2092
2217
|
/**
|
|
2093
|
-
*
|
|
2218
|
+
* Past this many files in the output directory, no snapshot of it is taken before a run.
|
|
2094
2219
|
*/
|
|
2095
|
-
const
|
|
2096
|
-
/**
|
|
2097
|
-
* Reads every file a run produced back out of its storage.
|
|
2098
|
-
*/
|
|
2099
|
-
async function readSnapshot(generation) {
|
|
2100
|
-
const snapshot = /* @__PURE__ */ new Map();
|
|
2101
|
-
await inParallel({
|
|
2102
|
-
items: [...generation.paths],
|
|
2103
|
-
limit: FILE_READ_CONCURRENCY,
|
|
2104
|
-
run: async (path) => {
|
|
2105
|
-
const content = await generation.storage.readItem(absoluteStoragePath(generation.root, path));
|
|
2106
|
-
if (content !== null) snapshot.set(path, content);
|
|
2107
|
-
}
|
|
2108
|
-
});
|
|
2109
|
-
return snapshot;
|
|
2110
|
-
}
|
|
2111
|
-
/**
|
|
2112
|
-
* How each path differs between two runs. Paths with identical content are left out.
|
|
2113
|
-
*/
|
|
2114
|
-
function diffSnapshots(previous, current) {
|
|
2115
|
-
const changes = {};
|
|
2116
|
-
for (const [path, content] of current) {
|
|
2117
|
-
if (!previous.has(path)) {
|
|
2118
|
-
changes[path] = "added";
|
|
2119
|
-
continue;
|
|
2120
|
-
}
|
|
2121
|
-
if (previous.get(path) !== content) changes[path] = "changed";
|
|
2122
|
-
}
|
|
2123
|
-
for (const path of previous.keys()) if (!current.has(path)) changes[path] = "removed";
|
|
2124
|
-
return changes;
|
|
2125
|
-
}
|
|
2220
|
+
const DISK_SNAPSHOT_MAX_FILES = 1e4;
|
|
2126
2221
|
var GenerationRunTarget = class extends capnweb.RpcTarget {
|
|
2127
2222
|
generationStream;
|
|
2128
2223
|
generationResult;
|
|
@@ -2221,7 +2316,8 @@ var StudioSession = class {
|
|
|
2221
2316
|
#isGenerating = false;
|
|
2222
2317
|
#heartbeatTimer;
|
|
2223
2318
|
#lastGeneration;
|
|
2224
|
-
#
|
|
2319
|
+
#store;
|
|
2320
|
+
#limits = resolveGenerationLimits();
|
|
2225
2321
|
/**
|
|
2226
2322
|
* Resolves when Studio calls {@link StudioSession.connect}. `studio:ready` waits on this so the
|
|
2227
2323
|
* host does not queue jobs before the agent session is registered.
|
|
@@ -2237,6 +2333,18 @@ var StudioSession = class {
|
|
|
2237
2333
|
get #isSandbox() {
|
|
2238
2334
|
return this.#session?.isSandbox === true;
|
|
2239
2335
|
}
|
|
2336
|
+
/**
|
|
2337
|
+
* Kept in the project's cache directory, so it survives a restart, except on a sandbox: its pool
|
|
2338
|
+
* sessions run every tenant's jobs, so it keeps them in memory. Looked up by job id only.
|
|
2339
|
+
*/
|
|
2340
|
+
get #generations() {
|
|
2341
|
+
this.#store ??= createGenerationStore({
|
|
2342
|
+
storage: this.#isSandbox ? (0, _kubb_core.memoryStorage)() : (0, _kubb_core.cacheStorage)({ root: this.#options.root }),
|
|
2343
|
+
maxCount: this.#limits.maxCount,
|
|
2344
|
+
maxMb: this.#limits.maxMb
|
|
2345
|
+
});
|
|
2346
|
+
return this.#store;
|
|
2347
|
+
}
|
|
2240
2348
|
get #canWrite() {
|
|
2241
2349
|
return !this.#isSandbox && this.#options.permissions.allowWrite;
|
|
2242
2350
|
}
|
|
@@ -2446,8 +2554,18 @@ var StudioSession = class {
|
|
|
2446
2554
|
await this.#warn(`Ignored the spec from Studio; set ${remedy} to generate from it`);
|
|
2447
2555
|
}
|
|
2448
2556
|
const resolvedPlugins = plugins ?? config.plugins;
|
|
2449
|
-
if (this.#lastGeneration) this.#previousGeneration = await readSnapshot(this.#lastGeneration);
|
|
2450
2557
|
this.#lastGeneration = void 0;
|
|
2558
|
+
const diskFiles = this.#hasProjectOnDisk ? await listDisk({
|
|
2559
|
+
root,
|
|
2560
|
+
outputPath: config.output.path,
|
|
2561
|
+
maxFiles: DISK_SNAPSHOT_MAX_FILES
|
|
2562
|
+
}) : void 0;
|
|
2563
|
+
const disk = diskFiles ? await this.#generations.keep({
|
|
2564
|
+
jobId: data.jobId,
|
|
2565
|
+
source: "disk",
|
|
2566
|
+
files: diskFiles,
|
|
2567
|
+
maxSetMb: this.#limits.maxSnapshotMb
|
|
2568
|
+
}) : void 0;
|
|
2451
2569
|
const detach = [setupHookListener(this.#hooks, root, controller.signal)];
|
|
2452
2570
|
try {
|
|
2453
2571
|
await generate({
|
|
@@ -2468,6 +2586,9 @@ var StudioSession = class {
|
|
|
2468
2586
|
hooks: this.#hooks,
|
|
2469
2587
|
signal: controller.signal
|
|
2470
2588
|
});
|
|
2589
|
+
} catch (error) {
|
|
2590
|
+
await this.#generations.drop(data.jobId);
|
|
2591
|
+
throw error;
|
|
2471
2592
|
} finally {
|
|
2472
2593
|
for (const remove of detach) remove();
|
|
2473
2594
|
}
|
|
@@ -2476,18 +2597,30 @@ var StudioSession = class {
|
|
|
2476
2597
|
info: `${resolvedPlugins.length} plugin${resolvedPlugins.length === 1 ? "" : "s"}, ${this.#canWrite ? "written to disk" : "in memory"}${inputOverride !== void 0 ? ", from a Studio spec" : ""}`
|
|
2477
2598
|
});
|
|
2478
2599
|
const generation = this.#lastGeneration;
|
|
2479
|
-
const
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2600
|
+
const output = generation ? await this.#generations.keep({
|
|
2601
|
+
jobId: data.jobId,
|
|
2602
|
+
source: "output",
|
|
2603
|
+
files: generation.output,
|
|
2604
|
+
maxSetMb: this.#limits.maxMb
|
|
2605
|
+
}) : void 0;
|
|
2606
|
+
if (generation && output) {
|
|
2607
|
+
if (!output.paths.length && Object.keys(output.hashes).length) await this.#warn("Kept only the hashes of this generation: its output is too large to keep");
|
|
2608
|
+
const { peerDependencies, missingDependencies } = generation;
|
|
2609
|
+
await this.#generations.add({
|
|
2610
|
+
jobId: data.jobId,
|
|
2611
|
+
output,
|
|
2612
|
+
disk,
|
|
2613
|
+
peerDependencies,
|
|
2614
|
+
missingDependencies
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
const files = [...generation?.output.paths ?? []];
|
|
2486
2618
|
return {
|
|
2487
2619
|
status: "success",
|
|
2488
2620
|
files,
|
|
2489
2621
|
fileCount: files.length,
|
|
2490
|
-
|
|
2622
|
+
hashes: output?.hashes ?? {},
|
|
2623
|
+
disk: disk ? { hashes: disk.hashes } : void 0
|
|
2491
2624
|
};
|
|
2492
2625
|
} finally {
|
|
2493
2626
|
this.#isGenerating = false;
|
|
@@ -2542,20 +2675,16 @@ var StudioSession = class {
|
|
|
2542
2675
|
if (this.#isSandbox) return this.#refuse("Ignored snapshot: a sandbox agent has no project to build a package from", "A sandbox agent has no project to build a package from");
|
|
2543
2676
|
const { name, version, bundledDependencies, uploadPath } = data;
|
|
2544
2677
|
if (!name || !version || !uploadPath) return this.#refuse("Ignored snapshot: the message was missing required fields", "The request was missing required fields");
|
|
2545
|
-
const generation = this.#
|
|
2678
|
+
const generation = await this.#generations.latest();
|
|
2546
2679
|
if (!generation) return this.#refuse("Ignored snapshot: no prior generation to pack", "No prior generation exists to pack, run a generation first");
|
|
2547
2680
|
const bundled = new Set(bundledDependencies ?? []);
|
|
2548
2681
|
const missing = generation.missingDependencies.filter((dependency) => !bundled.has(dependency));
|
|
2549
2682
|
if (missing.length) return this.#refuse(`Ignored snapshot: missing dependencies: ${missing.join(", ")}`, `Missing dependencies: ${missing.join(", ")}`);
|
|
2550
2683
|
try {
|
|
2551
|
-
const files = {
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
run: async (relativePath) => {
|
|
2556
|
-
const content = await generation.storage.readItem(absoluteStoragePath(generation.root, relativePath));
|
|
2557
|
-
if (content !== null) files[relativePath] = content;
|
|
2558
|
-
}
|
|
2684
|
+
const files = await this.#generations.read({
|
|
2685
|
+
generation,
|
|
2686
|
+
source: "output",
|
|
2687
|
+
paths: generation.output.paths
|
|
2559
2688
|
});
|
|
2560
2689
|
const { bytes, integrity } = await createSnapshotPackage(files, {
|
|
2561
2690
|
name,
|
|
@@ -2593,6 +2722,13 @@ var StudioSession = class {
|
|
|
2593
2722
|
throw error;
|
|
2594
2723
|
}
|
|
2595
2724
|
}
|
|
2725
|
+
/**
|
|
2726
|
+
* An agent with a project on disk can show a run against what its output directory held before.
|
|
2727
|
+
* A sandbox agent has no project.
|
|
2728
|
+
*/
|
|
2729
|
+
get #hasProjectOnDisk() {
|
|
2730
|
+
return !this.#isSandbox && this.#canRead;
|
|
2731
|
+
}
|
|
2596
2732
|
async readFiles(data) {
|
|
2597
2733
|
const command = "readFiles";
|
|
2598
2734
|
await this.#hooks.callHook("studio:command:start", { command });
|
|
@@ -2605,27 +2741,15 @@ var StudioSession = class {
|
|
|
2605
2741
|
if (!Array.isArray(data.paths)) return this.#refuse("Ignored files: the message carried no paths", "The request carried no paths");
|
|
2606
2742
|
const { paths } = data;
|
|
2607
2743
|
if (paths.length > 50) return this.#refuse(`Ignored files: requested ${paths.length} paths, more than the 50 allowed per request`, `At most 50 paths may be requested at once`);
|
|
2608
|
-
if (data.
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
}
|
|
2618
|
-
const generation = this.#lastGeneration;
|
|
2619
|
-
if (!generation) return this.#refuse("Ignored files: no prior generation to read from", "No prior generation to read from, run a generation first");
|
|
2620
|
-
const requested = paths.filter((path) => generation.paths.has(path));
|
|
2621
|
-
const files = {};
|
|
2622
|
-
await inParallel({
|
|
2623
|
-
items: requested,
|
|
2624
|
-
limit: FILE_READ_CONCURRENCY,
|
|
2625
|
-
run: async (path) => {
|
|
2626
|
-
const content = await generation.storage.readItem(absoluteStoragePath(generation.root, path));
|
|
2627
|
-
if (content !== null) files[path] = content;
|
|
2628
|
-
}
|
|
2744
|
+
if (typeof data.jobId !== "string" || !data.jobId) return this.#refuse("Ignored files: the message named no job", "The request named no generation job");
|
|
2745
|
+
const generation = await this.#generations.get(data.jobId);
|
|
2746
|
+
if (!generation) return this.#refuse(`Ignored files: job ${data.jobId} is not kept on this agent`, require_protocol.GENERATION_GONE_MESSAGE);
|
|
2747
|
+
const source = data.source === "disk" ? "disk" : "output";
|
|
2748
|
+
if (!generation[source]) return this.#refuse("Ignored files: that job has no snapshot of the files on disk", "This agent kept no snapshot of the files on disk for that job");
|
|
2749
|
+
const files = await this.#generations.read({
|
|
2750
|
+
generation,
|
|
2751
|
+
source,
|
|
2752
|
+
paths
|
|
2629
2753
|
});
|
|
2630
2754
|
await this.#hooks.callHook("studio:command:end", {
|
|
2631
2755
|
command,
|