@love-moon/conductor-cli 0.2.29 → 0.2.31
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/bin/conductor-config.js +2 -1
- package/bin/conductor-fire.js +85 -34
- package/package.json +4 -4
- package/src/daemon.js +359 -249
- package/src/fire/resume.js +101 -1
- package/src/native-deps.js +9 -7
- package/src/runtime-backends.js +219 -9
package/src/fire/resume.js
CHANGED
|
@@ -6,6 +6,11 @@ import readline from "node:readline";
|
|
|
6
6
|
import crypto from "node:crypto";
|
|
7
7
|
|
|
8
8
|
import yaml from "js-yaml";
|
|
9
|
+
import {
|
|
10
|
+
getExternalRuntimeBackendDescriptor,
|
|
11
|
+
isRuntimeSupportedBackend,
|
|
12
|
+
normalizeRuntimeBackendAlias,
|
|
13
|
+
} from "../runtime-backends.js";
|
|
9
14
|
|
|
10
15
|
function normalizeBackend(backend) {
|
|
11
16
|
return String(backend || "").trim().toLowerCase();
|
|
@@ -166,6 +171,10 @@ export async function resolveResumeContext(backend, sessionId, options = {}) {
|
|
|
166
171
|
}
|
|
167
172
|
const provider = resumeProviderForBackend(backend);
|
|
168
173
|
if (!provider) {
|
|
174
|
+
const externalContext = await resolveExternalResumeContext(backend, normalizedSessionId, options);
|
|
175
|
+
if (externalContext) {
|
|
176
|
+
return externalContext;
|
|
177
|
+
}
|
|
169
178
|
throw new Error(`--resume is not supported for backend "${backend}"`);
|
|
170
179
|
}
|
|
171
180
|
|
|
@@ -424,7 +433,10 @@ async function loadConductorSessionRecords(options = {}) {
|
|
|
424
433
|
if (!entry || typeof entry !== "object") {
|
|
425
434
|
continue;
|
|
426
435
|
}
|
|
427
|
-
records.push(
|
|
436
|
+
records.push({
|
|
437
|
+
...entry,
|
|
438
|
+
__conductorSourcePath: filePath,
|
|
439
|
+
});
|
|
428
440
|
}
|
|
429
441
|
}
|
|
430
442
|
return records;
|
|
@@ -434,6 +446,94 @@ function normalizeProjectPathCandidate(value) {
|
|
|
434
446
|
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
435
447
|
}
|
|
436
448
|
|
|
449
|
+
function normalizeConductorRecordSourcePath(value) {
|
|
450
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async function resolveExternalResumeContext(backend, sessionId, options = {}) {
|
|
454
|
+
const configFilePath =
|
|
455
|
+
typeof options?.configFilePath === "string" && options.configFilePath.trim()
|
|
456
|
+
? options.configFilePath.trim()
|
|
457
|
+
: undefined;
|
|
458
|
+
const normalizedBackend = await normalizeRuntimeBackendAlias(backend, { configFilePath });
|
|
459
|
+
if (!normalizedBackend || resumeProviderForBackend(normalizedBackend)) {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
if (!(await isRuntimeSupportedBackend(normalizedBackend, { configFilePath }))) {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const descriptor = await getExternalRuntimeBackendDescriptor(normalizedBackend, { configFilePath });
|
|
467
|
+
if (typeof descriptor?.resolveResumeContext === "function") {
|
|
468
|
+
const resolvedContext = await descriptor.resolveResumeContext(sessionId, options);
|
|
469
|
+
const cwd = normalizeProjectPathCandidate(resolvedContext?.cwd);
|
|
470
|
+
if (!cwd) {
|
|
471
|
+
throw new Error(`Could not resolve workspace for backend "${normalizedBackend}" session ${sessionId}`);
|
|
472
|
+
}
|
|
473
|
+
if (!(await isExistingDirectory(cwd))) {
|
|
474
|
+
throw new Error(`Resume workspace path does not exist: ${cwd}`);
|
|
475
|
+
}
|
|
476
|
+
const sessionPath = normalizeProjectPathCandidate(resolvedContext?.sessionPath);
|
|
477
|
+
return {
|
|
478
|
+
provider: normalizedBackend,
|
|
479
|
+
sessionId,
|
|
480
|
+
sessionPath,
|
|
481
|
+
cwd,
|
|
482
|
+
debugMetadata: {
|
|
483
|
+
cwdSource:
|
|
484
|
+
typeof resolvedContext?.debugMetadata?.cwdSource === "string" && resolvedContext.debugMetadata.cwdSource.trim()
|
|
485
|
+
? resolvedContext.debugMetadata.cwdSource.trim()
|
|
486
|
+
: "provider",
|
|
487
|
+
sessionPath,
|
|
488
|
+
...(resolvedContext?.debugMetadata && typeof resolvedContext.debugMetadata === "object"
|
|
489
|
+
? resolvedContext.debugMetadata
|
|
490
|
+
: {}),
|
|
491
|
+
},
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const records = await loadConductorSessionRecords(options);
|
|
496
|
+
for (const record of records) {
|
|
497
|
+
const recordSessionId = normalizeSessionId(record?.session_id);
|
|
498
|
+
const recordBackend = normalizeBackend(record?.backend_type);
|
|
499
|
+
const projectPath = normalizeProjectPathCandidate(record?.project_path);
|
|
500
|
+
if (recordSessionId !== sessionId || recordBackend !== normalizedBackend || !projectPath) {
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (!(await isExistingDirectory(projectPath))) {
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
const sessionPath = normalizeConductorRecordSourcePath(record?.__conductorSourcePath);
|
|
507
|
+
return {
|
|
508
|
+
provider: normalizedBackend,
|
|
509
|
+
sessionId,
|
|
510
|
+
sessionPath,
|
|
511
|
+
cwd: projectPath,
|
|
512
|
+
debugMetadata: {
|
|
513
|
+
cwdSource: "conductor_session_record",
|
|
514
|
+
sessionPath,
|
|
515
|
+
},
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
for (const candidate of listCandidateWorkingDirectories(options)) {
|
|
520
|
+
if (await isExistingDirectory(candidate)) {
|
|
521
|
+
return {
|
|
522
|
+
provider: normalizedBackend,
|
|
523
|
+
sessionId,
|
|
524
|
+
sessionPath: null,
|
|
525
|
+
cwd: candidate,
|
|
526
|
+
debugMetadata: {
|
|
527
|
+
cwdSource: "current_working_directory",
|
|
528
|
+
sessionPath: null,
|
|
529
|
+
},
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
throw new Error(`Could not resolve workspace for backend "${normalizedBackend}" session ${sessionId}`);
|
|
535
|
+
}
|
|
536
|
+
|
|
437
537
|
async function resolveKimiResumeCwd(sessionPath, sessionId, options = {}) {
|
|
438
538
|
const sessionDirectory = typeof sessionPath === "string" ? sessionPath.trim() : "";
|
|
439
539
|
if (!sessionDirectory) {
|
package/src/native-deps.js
CHANGED
|
@@ -315,8 +315,16 @@ export async function repairAndVerifyGlobalNodePty({
|
|
|
315
315
|
await ensurePnpmOnlyBuiltDependencies({ runCommand, dependencies, global: true });
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
+
const packageDirectory = await resolveGlobalPackageDirectory({
|
|
319
|
+
packageManager,
|
|
320
|
+
packageName,
|
|
321
|
+
runCommand,
|
|
322
|
+
});
|
|
323
|
+
|
|
318
324
|
if (packageManager === "pnpm") {
|
|
319
|
-
const rebuildResult = await runCommand("pnpm", ["rebuild",
|
|
325
|
+
const rebuildResult = await runCommand("pnpm", ["rebuild", ...dependencies], {
|
|
326
|
+
cwd: packageDirectory,
|
|
327
|
+
});
|
|
320
328
|
if (!rebuildResult.success) {
|
|
321
329
|
throw new Error(
|
|
322
330
|
`pnpm rebuild failed: ${String(rebuildResult.stderr || rebuildResult.stdout || "unknown error").trim()}`,
|
|
@@ -336,12 +344,6 @@ export async function repairAndVerifyGlobalNodePty({
|
|
|
336
344
|
);
|
|
337
345
|
}
|
|
338
346
|
}
|
|
339
|
-
|
|
340
|
-
const packageDirectory = await resolveGlobalPackageDirectory({
|
|
341
|
-
packageManager,
|
|
342
|
-
packageName,
|
|
343
|
-
runCommand,
|
|
344
|
-
});
|
|
345
347
|
await verifyNodePtyForPackageDirectory({
|
|
346
348
|
packageDirectory,
|
|
347
349
|
runCommand,
|
package/src/runtime-backends.js
CHANGED
|
@@ -1,22 +1,226 @@
|
|
|
1
|
-
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
import yaml from "js-yaml";
|
|
6
|
+
|
|
7
|
+
const BUILT_IN_RUNTIME_BACKENDS = ["codex", "claude", "kimi", "opencode"];
|
|
8
|
+
const BUILT_IN_RUNTIME_BACKEND_SET = new Set(BUILT_IN_RUNTIME_BACKENDS);
|
|
9
|
+
const LEGACY_RUNTIME_BACKEND_ALIASES = new Set([
|
|
10
|
+
"code",
|
|
11
|
+
"claude-code",
|
|
12
|
+
"open-code",
|
|
13
|
+
"open_code",
|
|
14
|
+
"kimi-cli",
|
|
15
|
+
"kimi-code",
|
|
16
|
+
]);
|
|
17
|
+
const externalRuntimeCatalogPromises = new Map();
|
|
18
|
+
let externalRuntimeImportNonce = 0;
|
|
19
|
+
|
|
20
|
+
function normalizeProviderPathEnv(value) {
|
|
21
|
+
return String(value || "").trim();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function listProviderModulePaths(providerPathEnv) {
|
|
25
|
+
const raw = normalizeProviderPathEnv(providerPathEnv);
|
|
26
|
+
if (!raw) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
return [...new Set(raw.split(process.platform === "win32" ? ";" : ":").map((item) => item.trim()).filter(Boolean))];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeRuntimeBackendName(backend) {
|
|
4
33
|
return String(backend || "").trim().toLowerCase();
|
|
5
34
|
}
|
|
6
35
|
|
|
7
|
-
|
|
8
|
-
|
|
36
|
+
function readConfigEnvValue(configFilePath, key) {
|
|
37
|
+
const targetPath =
|
|
38
|
+
typeof configFilePath === "string" && configFilePath.trim()
|
|
39
|
+
? path.resolve(configFilePath.trim())
|
|
40
|
+
: path.join(process.env.HOME || "", ".conductor", "config.yaml");
|
|
41
|
+
try {
|
|
42
|
+
if (!targetPath || !fs.existsSync(targetPath)) {
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
const parsed = yaml.load(fs.readFileSync(targetPath, "utf8"));
|
|
46
|
+
if (!parsed || typeof parsed !== "object") {
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
49
|
+
const value = parsed?.envs?.[key];
|
|
50
|
+
return typeof value === "string" ? value.trim() : "";
|
|
51
|
+
} catch {
|
|
52
|
+
return "";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resolveProviderPathEnv(options = {}) {
|
|
57
|
+
return (
|
|
58
|
+
normalizeProviderPathEnv(process.env.AISDK_PROVIDER_PATH) ||
|
|
59
|
+
normalizeProviderPathEnv(readConfigEnvValue(options.configFilePath, "AISDK_PROVIDER_PATH"))
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function createEmptyExternalCatalog() {
|
|
64
|
+
return {
|
|
65
|
+
backends: [],
|
|
66
|
+
backendSet: new Set(),
|
|
67
|
+
aliasToBackend: new Map(),
|
|
68
|
+
descriptors: [],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function validateDescriptor(descriptor, sourcePath) {
|
|
73
|
+
if (!descriptor || typeof descriptor !== "object") {
|
|
74
|
+
throw new Error(`External AI SDK provider module ${sourcePath} contains an invalid provider descriptor.`);
|
|
75
|
+
}
|
|
76
|
+
const backend = normalizeRuntimeBackendName(descriptor.backend);
|
|
77
|
+
if (!backend) {
|
|
78
|
+
throw new Error(`External AI SDK provider module ${sourcePath} is missing provider.backend.`);
|
|
79
|
+
}
|
|
80
|
+
if (BUILT_IN_RUNTIME_BACKEND_SET.has(backend)) {
|
|
81
|
+
throw new Error(`External AI SDK provider backend "${backend}" from ${sourcePath} conflicts with a built-in backend.`);
|
|
82
|
+
}
|
|
83
|
+
if (LEGACY_RUNTIME_BACKEND_ALIASES.has(backend)) {
|
|
84
|
+
throw new Error(`External AI SDK provider backend "${backend}" from ${sourcePath} conflicts with a reserved CLI alias.`);
|
|
85
|
+
}
|
|
86
|
+
const variant = String(descriptor.variant || "").trim();
|
|
87
|
+
if (!variant) {
|
|
88
|
+
throw new Error(`External AI SDK provider "${backend}" from ${sourcePath} is missing provider.variant.`);
|
|
89
|
+
}
|
|
90
|
+
if (typeof descriptor.createSession !== "function") {
|
|
91
|
+
throw new Error(`External AI SDK provider "${backend}" from ${sourcePath} is missing provider.createSession().`);
|
|
92
|
+
}
|
|
93
|
+
const aliases = Array.isArray(descriptor.aliases)
|
|
94
|
+
? descriptor.aliases.map((item) => normalizeRuntimeBackendName(item)).filter(Boolean)
|
|
95
|
+
: [];
|
|
96
|
+
return {
|
|
97
|
+
backend,
|
|
98
|
+
aliases,
|
|
99
|
+
resolveResumeContext: typeof descriptor.resolveResumeContext === "function" ? descriptor.resolveResumeContext : null,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function importExternalProviderModule(modulePath) {
|
|
104
|
+
const resolvedPath = path.isAbsolute(modulePath) ? modulePath : path.resolve(modulePath);
|
|
105
|
+
const moduleUrl = pathToFileURL(resolvedPath);
|
|
106
|
+
moduleUrl.searchParams.set("conductor-external-provider-attempt", String(++externalRuntimeImportNonce));
|
|
107
|
+
return import(moduleUrl.href);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function registerExternalAlias(catalog, alias, backend, sourcePath) {
|
|
111
|
+
if (!alias) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (BUILT_IN_RUNTIME_BACKEND_SET.has(alias)) {
|
|
115
|
+
throw new Error(`External AI SDK provider alias "${alias}" from ${sourcePath} conflicts with a built-in backend.`);
|
|
116
|
+
}
|
|
117
|
+
if (LEGACY_RUNTIME_BACKEND_ALIASES.has(alias)) {
|
|
118
|
+
throw new Error(`External AI SDK provider alias "${alias}" from ${sourcePath} conflicts with a reserved CLI alias.`);
|
|
119
|
+
}
|
|
120
|
+
const existingBackend = catalog.aliasToBackend.get(alias);
|
|
121
|
+
if (existingBackend && existingBackend !== backend) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`External AI SDK provider alias "${alias}" from ${sourcePath} conflicts with backend "${existingBackend}".`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
catalog.aliasToBackend.set(alias, backend);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function loadExternalRuntimeCatalog(providerPathEnv) {
|
|
130
|
+
const catalog = createEmptyExternalCatalog();
|
|
131
|
+
for (const modulePath of listProviderModulePaths(providerPathEnv)) {
|
|
132
|
+
let importedModule;
|
|
133
|
+
try {
|
|
134
|
+
importedModule = await importExternalProviderModule(modulePath);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
throw new Error(`Failed to load external AI SDK provider module ${modulePath}: ${error?.message || error}`);
|
|
137
|
+
}
|
|
138
|
+
const providers = Array.isArray(importedModule?.providers) ? importedModule.providers : [];
|
|
139
|
+
if (providers.length === 0) {
|
|
140
|
+
throw new Error(`External AI SDK provider module ${modulePath} must export a non-empty providers array.`);
|
|
141
|
+
}
|
|
142
|
+
for (const rawDescriptor of providers) {
|
|
143
|
+
const descriptor = validateDescriptor(rawDescriptor, modulePath);
|
|
144
|
+
if (catalog.backendSet.has(descriptor.backend)) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`External AI SDK provider backend "${descriptor.backend}" is declared more than once (latest: ${modulePath}).`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
catalog.descriptors.push(descriptor);
|
|
150
|
+
catalog.backends.push(descriptor.backend);
|
|
151
|
+
catalog.backendSet.add(descriptor.backend);
|
|
152
|
+
registerExternalAlias(catalog, descriptor.backend, descriptor.backend, modulePath);
|
|
153
|
+
for (const alias of descriptor.aliases) {
|
|
154
|
+
registerExternalAlias(catalog, alias, descriptor.backend, modulePath);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return catalog;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function getExternalRuntimeCatalog(options = {}) {
|
|
162
|
+
const providerPathEnv = resolveProviderPathEnv(options);
|
|
163
|
+
if (!externalRuntimeCatalogPromises.has(providerPathEnv)) {
|
|
164
|
+
const loadPromise = loadExternalRuntimeCatalog(providerPathEnv).catch((error) => {
|
|
165
|
+
externalRuntimeCatalogPromises.delete(providerPathEnv);
|
|
166
|
+
throw error;
|
|
167
|
+
});
|
|
168
|
+
externalRuntimeCatalogPromises.set(providerPathEnv, loadPromise);
|
|
169
|
+
}
|
|
170
|
+
return externalRuntimeCatalogPromises.get(providerPathEnv);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function normalizeRuntimeBackendAlias(backend, options = {}) {
|
|
174
|
+
const normalized = normalizeRuntimeBackendName(backend);
|
|
175
|
+
if (!normalized) {
|
|
176
|
+
return "";
|
|
177
|
+
}
|
|
178
|
+
if (LEGACY_RUNTIME_BACKEND_ALIASES.has(normalized) || BUILT_IN_RUNTIME_BACKEND_SET.has(normalized)) {
|
|
179
|
+
return normalized;
|
|
180
|
+
}
|
|
181
|
+
const catalog = await getExternalRuntimeCatalog(options);
|
|
182
|
+
return catalog.aliasToBackend.get(normalized) || normalized;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function listRuntimeSupportedBackends(options = {}) {
|
|
186
|
+
const catalog = await getExternalRuntimeCatalog(options);
|
|
187
|
+
return [...BUILT_IN_RUNTIME_BACKENDS, ...catalog.backends];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export async function getExternalRuntimeBackendDescriptor(backend, options = {}) {
|
|
191
|
+
const normalized = await normalizeRuntimeBackendAlias(backend, options);
|
|
192
|
+
if (!normalized || BUILT_IN_RUNTIME_BACKEND_SET.has(normalized) || LEGACY_RUNTIME_BACKEND_ALIASES.has(normalized)) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
const catalog = await getExternalRuntimeCatalog(options);
|
|
196
|
+
return catalog.backendSet.has(normalized)
|
|
197
|
+
? {
|
|
198
|
+
backend: normalized,
|
|
199
|
+
...(catalog.descriptors.find((descriptor) => descriptor.backend === normalized) || {}),
|
|
200
|
+
}
|
|
201
|
+
: null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function isRuntimeSupportedBackend(backend, options = {}) {
|
|
205
|
+
const normalized = await normalizeRuntimeBackendAlias(backend, options);
|
|
206
|
+
if (BUILT_IN_RUNTIME_BACKEND_SET.has(normalized)) {
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
if (LEGACY_RUNTIME_BACKEND_ALIASES.has(normalized)) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
const catalog = await getExternalRuntimeCatalog(options);
|
|
213
|
+
return catalog.backendSet.has(normalized);
|
|
9
214
|
}
|
|
10
215
|
|
|
11
|
-
export function filterRuntimeSupportedAllowCliList(allowCliList) {
|
|
216
|
+
export async function filterRuntimeSupportedAllowCliList(allowCliList, options = {}) {
|
|
12
217
|
if (!allowCliList || typeof allowCliList !== "object") {
|
|
13
218
|
return {};
|
|
14
219
|
}
|
|
15
|
-
|
|
16
220
|
const filtered = {};
|
|
17
221
|
for (const [backend, command] of Object.entries(allowCliList)) {
|
|
18
|
-
const normalizedBackend =
|
|
19
|
-
if (!
|
|
222
|
+
const normalizedBackend = await normalizeRuntimeBackendAlias(backend, options);
|
|
223
|
+
if (!(await isRuntimeSupportedBackend(normalizedBackend, options))) {
|
|
20
224
|
continue;
|
|
21
225
|
}
|
|
22
226
|
if (typeof command !== "string" || !command.trim()) {
|
|
@@ -25,7 +229,13 @@ export function filterRuntimeSupportedAllowCliList(allowCliList) {
|
|
|
25
229
|
if (filtered[normalizedBackend] !== undefined) {
|
|
26
230
|
continue;
|
|
27
231
|
}
|
|
28
|
-
filtered[normalizedBackend] = command;
|
|
232
|
+
filtered[normalizedBackend] = command.trim();
|
|
29
233
|
}
|
|
30
234
|
return filtered;
|
|
31
235
|
}
|
|
236
|
+
|
|
237
|
+
export { BUILT_IN_RUNTIME_BACKENDS as RUNTIME_SUPPORTED_BACKENDS, normalizeRuntimeBackendName };
|
|
238
|
+
|
|
239
|
+
export function resetRuntimeBackendCacheForTests() {
|
|
240
|
+
externalRuntimeCatalogPromises.clear();
|
|
241
|
+
}
|