@agent-vm/agent-vm 0.0.69 → 0.0.70
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/build/managed-image-dockerfile.d.ts.map +1 -1
- package/dist/build/managed-image-dockerfile.js +6 -9
- package/dist/build/managed-image-dockerfile.js.map +1 -1
- package/dist/cli/agent-vm-cli-support.d.ts +2 -2
- package/dist/cli/agent-vm-cli-support.d.ts.map +1 -1
- package/dist/cli/agent-vm-cli-support.js +2 -1
- package/dist/cli/agent-vm-cli-support.js.map +1 -1
- package/dist/cli/commands/create-app.d.ts +62 -78
- package/dist/cli/commands/create-app.d.ts.map +1 -1
- package/dist/cli/commands/migrate-definition.d.ts +0 -16
- package/dist/cli/commands/migrate-definition.d.ts.map +1 -1
- package/dist/cli/commands/migrate-definition.js +1 -20
- package/dist/cli/commands/migrate-definition.js.map +1 -1
- package/dist/cli/init-command.d.ts.map +1 -1
- package/dist/cli/init-command.js +7 -37
- package/dist/cli/init-command.js.map +1 -1
- package/dist/cli/manual-templates.d.ts.map +1 -1
- package/dist/cli/manual-templates.js +9 -7
- package/dist/cli/manual-templates.js.map +1 -1
- package/dist/cli/migrate-commands.d.ts +0 -8
- package/dist/cli/migrate-commands.d.ts.map +1 -1
- package/dist/cli/migrate-commands.js +0 -274
- package/dist/cli/migrate-commands.js.map +1 -1
- package/dist/config/system-config.d.ts +2 -1
- package/dist/config/system-config.d.ts.map +1 -1
- package/dist/config/system-config.js +24 -4
- package/dist/config/system-config.js.map +1 -1
- package/dist/controller/controller-runtime-operations.d.ts +1 -1
- package/dist/controller/controller-runtime-operations.d.ts.map +1 -1
- package/dist/controller/controller-runtime-support.d.ts +1 -2
- package/dist/controller/controller-runtime-support.d.ts.map +1 -1
- package/dist/controller/controller-runtime-support.js +1 -2
- package/dist/controller/controller-runtime-support.js.map +1 -1
- package/dist/controller/controller-runtime-types.d.ts +1 -1
- package/dist/controller/controller-runtime-types.d.ts.map +1 -1
- package/dist/controller/controller-runtime.d.ts.map +1 -1
- package/dist/controller/controller-runtime.js +4 -4
- package/dist/controller/controller-runtime.js.map +1 -1
- package/dist/controller/http/controller-http-routes.d.ts +1 -1
- package/dist/controller/http/controller-http-routes.d.ts.map +1 -1
- package/dist/controller/leases/agent-sandbox-seeding.d.ts +1 -1
- package/dist/controller/leases/agent-sandbox-seeding.d.ts.map +1 -1
- package/dist/controller/worker-task-runner.d.ts +1 -1
- package/dist/controller/worker-task-runner.d.ts.map +1 -1
- package/dist/controller/zone-runtimes/openclaw-zone-runtime.d.ts +1 -1
- package/dist/controller/zone-runtimes/openclaw-zone-runtime.d.ts.map +1 -1
- package/dist/controller/zone-runtimes/worker-zone-runtime.d.ts +1 -1
- package/dist/controller/zone-runtimes/worker-zone-runtime.d.ts.map +1 -1
- package/dist/controller/zone-runtimes/zone-runtime-types.d.ts +2 -1
- package/dist/controller/zone-runtimes/zone-runtime-types.d.ts.map +1 -1
- package/dist/gateway/credential-manager.d.ts +1 -1
- package/dist/gateway/credential-manager.d.ts.map +1 -1
- package/dist/gateway/gateway-zone-orchestrator.d.ts.map +1 -1
- package/dist/gateway/gateway-zone-orchestrator.js +44 -16
- package/dist/gateway/gateway-zone-orchestrator.js.map +1 -1
- package/dist/gateway/gateway-zone-support.d.ts +1 -1
- package/dist/gateway/gateway-zone-support.d.ts.map +1 -1
- package/dist/gateway/gateway-zone-support.js +2 -1
- package/dist/gateway/gateway-zone-support.js.map +1 -1
- package/dist/gateway/mcp-portal-effective-config.d.ts +26 -0
- package/dist/gateway/mcp-portal-effective-config.d.ts.map +1 -0
- package/dist/gateway/mcp-portal-effective-config.js +237 -0
- package/dist/gateway/mcp-portal-effective-config.js.map +1 -0
- package/dist/integration-tests/smoke-harness.d.ts +14 -1
- package/dist/integration-tests/smoke-harness.d.ts.map +1 -1
- package/dist/integration-tests/smoke-harness.js +404 -87
- package/dist/integration-tests/smoke-harness.js.map +1 -1
- package/dist/operations/config-validation.d.ts.map +1 -1
- package/dist/operations/config-validation.js +32 -2
- package/dist/operations/config-validation.js.map +1 -1
- package/dist/operations/openclaw-deployment-doctor.d.ts +0 -6
- package/dist/operations/openclaw-deployment-doctor.d.ts.map +1 -1
- package/dist/operations/openclaw-deployment-doctor.js +9 -83
- package/dist/operations/openclaw-deployment-doctor.js.map +1 -1
- package/dist/tool-vm/tool-vm-lifecycle.d.ts +2 -1
- package/dist/tool-vm/tool-vm-lifecycle.d.ts.map +1 -1
- package/dist/tool-vm/tool-vm-lifecycle.js +1 -1
- package/dist/tool-vm/tool-vm-lifecycle.js.map +1 -1
- package/package.json +11 -10
- package/dist/controller/composite-secret-resolver.d.ts +0 -3
- package/dist/controller/composite-secret-resolver.d.ts.map +0 -1
- package/dist/controller/composite-secret-resolver.js +0 -103
- package/dist/controller/composite-secret-resolver.js.map +0 -1
- package/dist/gateway/mcp-portal-openclaw-materialization.d.ts +0 -19
- package/dist/gateway/mcp-portal-openclaw-materialization.d.ts.map +0 -1
- package/dist/gateway/mcp-portal-openclaw-materialization.js +0 -26
- package/dist/gateway/mcp-portal-openclaw-materialization.js.map +0 -1
|
@@ -1,16 +1,28 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process';
|
|
1
|
+
import { execFile, execFileSync } from 'node:child_process';
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import net from 'node:net';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import {
|
|
6
|
+
import { promisify } from 'node:util';
|
|
7
|
+
import { resolveGondolinMinimumZigVersion } from '@agent-vm/gondolin-adapter';
|
|
7
8
|
import { computeFingerprintFromConfigPath } from '../build/gondolin-image-builder.js';
|
|
8
9
|
import { resolveManagedImageRelease } from '../build/managed-image-dockerfile.js';
|
|
9
10
|
import { isZigVersionAtLeast, resolveHostZigVersion } from '../build/zig-compatibility.js';
|
|
10
11
|
import { scaffoldAgentVmProject } from '../cli/init-command.js';
|
|
12
|
+
import { loadJsonConfigFile } from '../config/json-config-file.js';
|
|
11
13
|
import { loadSystemConfig } from '../config/system-config.js';
|
|
12
14
|
import { startControllerRuntime } from '../controller/controller-runtime.js';
|
|
13
15
|
import { startGatewayZone } from '../gateway/gateway-zone-orchestrator.js';
|
|
16
|
+
const defaultOpenClawMcpPortalExtensionsPath = '/home/openclaw/.openclaw/extensions/mcp-portal';
|
|
17
|
+
const execFileAsync = promisify(execFile);
|
|
18
|
+
const openClawMcpPortalPluginName = 'mcp-portal';
|
|
19
|
+
const smokeTempRootPrefixes = [
|
|
20
|
+
'agent-vm-gateway-smoke-project-',
|
|
21
|
+
'agent-vm-smoke-harness-',
|
|
22
|
+
'openclaw-mcp-portal-smoke-',
|
|
23
|
+
'openclaw-zone-git-smoke-',
|
|
24
|
+
'worker-loop-smoke-',
|
|
25
|
+
];
|
|
14
26
|
export function hasCommand(command) {
|
|
15
27
|
try {
|
|
16
28
|
execFileSync('sh', ['-lc', `command -v ${command} >/dev/null`], { stdio: 'ignore' });
|
|
@@ -26,6 +38,22 @@ export function currentSmokeArchitecture() {
|
|
|
26
38
|
export function qemuCommandForArchitecture(architecture) {
|
|
27
39
|
return architecture === 'aarch64' ? 'qemu-system-aarch64' : 'qemu-system-x86_64';
|
|
28
40
|
}
|
|
41
|
+
function isOwnedSmokeTempRoot(tempRoot) {
|
|
42
|
+
const resolvedTempRoot = path.resolve(tempRoot);
|
|
43
|
+
const resolvedSystemTempRoot = path.resolve(os.tmpdir());
|
|
44
|
+
if (resolvedTempRoot === resolvedSystemTempRoot ||
|
|
45
|
+
!resolvedTempRoot.startsWith(`${resolvedSystemTempRoot}${path.sep}`)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
const basename = path.basename(resolvedTempRoot);
|
|
49
|
+
return smokeTempRootPrefixes.some((prefix) => basename.startsWith(prefix));
|
|
50
|
+
}
|
|
51
|
+
export async function removeSmokeTempRoot(tempRoot) {
|
|
52
|
+
if (!isOwnedSmokeTempRoot(tempRoot)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
await fs.rm(tempRoot, { force: true, recursive: true });
|
|
56
|
+
}
|
|
29
57
|
export async function canRunGondolinSmoke(options) {
|
|
30
58
|
const commandExists = options.commandExists ?? hasCommand;
|
|
31
59
|
if (!commandExists(qemuCommandForArchitecture(options.architecture)) ||
|
|
@@ -92,11 +120,15 @@ export async function waitForControllerReady(controllerPort) {
|
|
|
92
120
|
throw new Error('Controller did not become ready in time.');
|
|
93
121
|
}
|
|
94
122
|
export async function findReusableGatewayImageDirectory(currentProjectRoot, gatewayBuildConfigPath) {
|
|
123
|
+
const explicitSmokeCacheRoot = process.env.AGENT_VM_SMOKE_CACHE_DIR;
|
|
124
|
+
if (!explicitSmokeCacheRoot) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
95
127
|
const requiredFingerprint = await computeFingerprintFromConfigPath(gatewayBuildConfigPath);
|
|
96
|
-
const tempRootEntries = await fs.readdir(
|
|
128
|
+
const tempRootEntries = await fs.readdir(explicitSmokeCacheRoot, { withFileTypes: true });
|
|
97
129
|
const smokeRunDirectories = tempRootEntries
|
|
98
130
|
.filter((entry) => entry.isDirectory() && entry.name.includes('-smoke-'))
|
|
99
|
-
.map((entry) => path.join(
|
|
131
|
+
.map((entry) => path.join(explicitSmokeCacheRoot, entry.name));
|
|
100
132
|
for (const smokeRunDirectory of smokeRunDirectories) {
|
|
101
133
|
if (smokeRunDirectory === currentProjectRoot) {
|
|
102
134
|
continue;
|
|
@@ -185,60 +217,211 @@ function getWorkerSmokeZone(systemConfig) {
|
|
|
185
217
|
}
|
|
186
218
|
return { ...zone, gateway: zone.gateway };
|
|
187
219
|
}
|
|
188
|
-
async function archivePackageDist(props) {
|
|
189
|
-
await fs.access(path.join(props.distDirectory, 'index.js'));
|
|
190
|
-
execFileSync('tar', ['--no-xattrs', '-czf', props.archivePath, '-C', props.distDirectory, '.'], {
|
|
191
|
-
env: { ...process.env, COPYFILE_DISABLE: '1' },
|
|
192
|
-
stdio: 'inherit',
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
220
|
async function packLocalPackageTarball(props) {
|
|
196
221
|
const packageJsonPath = path.join(props.packageDirectory, 'package.json');
|
|
197
222
|
await fs.access(packageJsonPath);
|
|
198
223
|
const packDirectory = await fs.mkdtemp(path.join(os.tmpdir(), `${props.packageName}-pack-`));
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
224
|
+
try {
|
|
225
|
+
execFileSync('pnpm', ['pack', '--pack-destination', packDirectory], {
|
|
226
|
+
cwd: props.packageDirectory,
|
|
227
|
+
stdio: 'pipe',
|
|
228
|
+
});
|
|
229
|
+
const packedTarballs = (await fs.readdir(packDirectory)).filter((fileName) => fileName.endsWith('.tgz'));
|
|
230
|
+
const [packedTarballName] = packedTarballs;
|
|
231
|
+
if (packedTarballName === undefined) {
|
|
232
|
+
throw new Error(`Failed to pack local ${props.packageName} tarball for smoke image.`);
|
|
233
|
+
}
|
|
234
|
+
if (packedTarballs.length > 1) {
|
|
235
|
+
throw new Error(`Expected pnpm pack for ${props.packageName} to produce exactly one tarball.`);
|
|
236
|
+
}
|
|
237
|
+
return path.join(packDirectory, packedTarballName);
|
|
207
238
|
}
|
|
208
|
-
|
|
209
|
-
|
|
239
|
+
catch (error) {
|
|
240
|
+
await fs.rm(packDirectory, { force: true, recursive: true });
|
|
241
|
+
throw error;
|
|
210
242
|
}
|
|
211
|
-
return path.join(packDirectory, packedTarballName);
|
|
212
243
|
}
|
|
213
|
-
async function
|
|
244
|
+
async function removeLocalPackageTarballDirectories(tarballPaths) {
|
|
245
|
+
await Promise.all(Array.from(new Set(tarballPaths
|
|
246
|
+
.filter((tarballPath) => tarballPath !== undefined)
|
|
247
|
+
.map((tarballPath) => path.dirname(tarballPath)))).map(async (packDirectory) => {
|
|
248
|
+
await fs.rm(packDirectory, { force: true, recursive: true });
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
async function copyLocalPackageTarballsToDockerContext(options) {
|
|
252
|
+
await Promise.all(options.tarballs.map(async (tarball) => {
|
|
253
|
+
await fs.copyFile(tarball.sourcePath, path.join(options.dockerContextDirectory, tarball.archiveName));
|
|
254
|
+
}));
|
|
255
|
+
}
|
|
256
|
+
async function useLocalToolVmMcpPortalPackageTarballs(options) {
|
|
214
257
|
const managedImageRelease = await resolveManagedImageRelease();
|
|
215
258
|
const baseImage = managedImageRelease.baseImages['tool-vm'];
|
|
216
259
|
const toolVmProfiles = Object.entries(options.systemConfig.imageProfiles.toolVms);
|
|
217
|
-
|
|
260
|
+
await Promise.all(toolVmProfiles.map(async ([profileName, toolVmProfile]) => {
|
|
218
261
|
const dockerContextDirectory = path.join(options.projectRoot, 'vm-images', 'tool-vms', `${profileName}-local-mcp-portal`);
|
|
219
262
|
const dockerfilePath = path.join(dockerContextDirectory, 'Dockerfile');
|
|
220
263
|
const localConfigContractsTarballName = 'config-contracts-local.tgz';
|
|
264
|
+
const localSecretsTarballName = 'secrets-local.tgz';
|
|
221
265
|
const localTarballName = 'mcp-portal-local.tgz';
|
|
222
266
|
await fs.rm(dockerContextDirectory, { force: true, recursive: true });
|
|
223
267
|
await fs.mkdir(dockerContextDirectory, { recursive: true });
|
|
224
|
-
await
|
|
225
|
-
|
|
268
|
+
await copyLocalPackageTarballsToDockerContext({
|
|
269
|
+
dockerContextDirectory,
|
|
270
|
+
tarballs: [
|
|
271
|
+
{
|
|
272
|
+
archiveName: localConfigContractsTarballName,
|
|
273
|
+
sourcePath: options.localConfigContractsTarballPath,
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
archiveName: localSecretsTarballName,
|
|
277
|
+
sourcePath: options.localSecretsTarballPath,
|
|
278
|
+
},
|
|
279
|
+
{ archiveName: localTarballName, sourcePath: options.localMcpPortalTarballPath },
|
|
280
|
+
],
|
|
281
|
+
});
|
|
226
282
|
await fs.writeFile(dockerfilePath, [
|
|
227
283
|
`FROM ${baseImage.repository}:${baseImage.tag}`,
|
|
228
284
|
'',
|
|
229
285
|
'# Generated by the OpenClaw smoke harness from the local MCP Portal package.',
|
|
230
286
|
'COPY config-contracts-local.tgz /tmp/config-contracts-local.tgz',
|
|
287
|
+
'COPY secrets-local.tgz /tmp/secrets-local.tgz',
|
|
231
288
|
'COPY mcp-portal-local.tgz /tmp/mcp-portal-local.tgz',
|
|
232
|
-
|
|
233
|
-
'
|
|
234
|
-
'
|
|
235
|
-
'RUN pnpm add -g /tmp/config-contracts-local.tgz /tmp/mcp-portal-local.tgz && rm -f /tmp/config-contracts-local.tgz /tmp/mcp-portal-local.tgz',
|
|
289
|
+
'RUN mkdir -p /opt/agent-vm/local-packages && \\',
|
|
290
|
+
' npm install --omit=dev --no-audit --no-fund --prefix /opt/agent-vm/local-packages /tmp/config-contracts-local.tgz /tmp/secrets-local.tgz /tmp/mcp-portal-local.tgz && \\',
|
|
291
|
+
' rm -f /tmp/config-contracts-local.tgz /tmp/secrets-local.tgz /tmp/mcp-portal-local.tgz',
|
|
236
292
|
'',
|
|
237
293
|
].join('\n'), 'utf8');
|
|
238
294
|
toolVmProfile.dockerfile = dockerfilePath;
|
|
239
295
|
delete toolVmProfile.source;
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
298
|
+
export async function useLocalToolVmMcpPortalPackage(options) {
|
|
299
|
+
const localConfigContractsTarballPath = await packLocalPackageTarball({
|
|
300
|
+
packageDirectory: path.join(options.repoRoot, 'packages', 'config-contracts'),
|
|
301
|
+
packageName: 'config-contracts',
|
|
302
|
+
});
|
|
303
|
+
const localSecretsTarballPath = await packLocalPackageTarball({
|
|
304
|
+
packageDirectory: path.join(options.repoRoot, 'packages', 'secrets'),
|
|
305
|
+
packageName: 'secrets',
|
|
306
|
+
});
|
|
307
|
+
const localMcpPortalTarballPath = await packLocalPackageTarball({
|
|
308
|
+
packageDirectory: path.join(options.repoRoot, 'packages', 'mcp-portal'),
|
|
309
|
+
packageName: 'mcp-portal',
|
|
310
|
+
});
|
|
311
|
+
try {
|
|
312
|
+
await useLocalToolVmMcpPortalPackageTarballs({
|
|
313
|
+
localConfigContractsTarballPath,
|
|
314
|
+
localMcpPortalTarballPath,
|
|
315
|
+
localSecretsTarballPath,
|
|
316
|
+
projectRoot: options.projectRoot,
|
|
317
|
+
systemConfig: options.systemConfig,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
finally {
|
|
321
|
+
await removeLocalPackageTarballDirectories([
|
|
322
|
+
localConfigContractsTarballPath,
|
|
323
|
+
localSecretsTarballPath,
|
|
324
|
+
localMcpPortalTarballPath,
|
|
325
|
+
]);
|
|
240
326
|
}
|
|
241
327
|
}
|
|
328
|
+
function localPackageTarballArchiveName(packageName) {
|
|
329
|
+
return `${packageName}-local.tgz`;
|
|
330
|
+
}
|
|
331
|
+
function isJsonRecord(value) {
|
|
332
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
333
|
+
}
|
|
334
|
+
function mutableJsonRecord(value) {
|
|
335
|
+
if (!isJsonRecord(value)) {
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
return value;
|
|
339
|
+
}
|
|
340
|
+
async function runDockerCommand(args) {
|
|
341
|
+
await execFileAsync('docker', [...args]);
|
|
342
|
+
}
|
|
343
|
+
async function readSmokeDockerImageTag(buildConfigPath) {
|
|
344
|
+
let buildConfig;
|
|
345
|
+
try {
|
|
346
|
+
buildConfig = mutableJsonRecord(await loadJsonConfigFile(buildConfigPath));
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
if (isJsonRecord(error) && error.code === 'ENOENT') {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
throw error;
|
|
353
|
+
}
|
|
354
|
+
const ociConfig = mutableJsonRecord(buildConfig?.oci);
|
|
355
|
+
const imageTag = ociConfig?.image;
|
|
356
|
+
return typeof imageTag === 'string' && imageTag.length > 0 ? imageTag : null;
|
|
357
|
+
}
|
|
358
|
+
export async function collectSmokeDockerImageTags(systemConfig) {
|
|
359
|
+
const imageProfiles = [
|
|
360
|
+
...Object.values(systemConfig.imageProfiles.gateways),
|
|
361
|
+
...Object.values(systemConfig.imageProfiles.toolVms),
|
|
362
|
+
];
|
|
363
|
+
const imageTags = [];
|
|
364
|
+
for (const imageProfile of imageProfiles) {
|
|
365
|
+
// oxlint-disable-next-line eslint/no-await-in-loop -- config files are intentionally read deterministically
|
|
366
|
+
const imageTag = await readSmokeDockerImageTag(imageProfile.buildConfig);
|
|
367
|
+
if (imageTag !== null) {
|
|
368
|
+
imageTags.push(imageTag);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return Array.from(new Set(imageTags));
|
|
372
|
+
}
|
|
373
|
+
export async function removeSmokeDockerImages(imageTags, options = {}) {
|
|
374
|
+
const dockerCommand = options.runDockerCommand ?? runDockerCommand;
|
|
375
|
+
for (const imageTag of Array.from(new Set(imageTags))) {
|
|
376
|
+
try {
|
|
377
|
+
// oxlint-disable-next-line eslint/no-await-in-loop -- one tag at a time keeps cleanup errors attributable
|
|
378
|
+
await dockerCommand(['image', 'inspect', imageTag]);
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
// oxlint-disable-next-line eslint/no-await-in-loop -- one tag at a time keeps cleanup errors attributable
|
|
384
|
+
await dockerCommand(['image', 'rm', '--force', imageTag]);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
export async function removeSmokeDockerImagesForSystemConfig(systemConfig, options = {}) {
|
|
388
|
+
await removeSmokeDockerImages(await collectSmokeDockerImageTags(systemConfig), options);
|
|
389
|
+
}
|
|
390
|
+
function throwIfSmokeHarnessCleanupFailed(errors) {
|
|
391
|
+
if (errors.length === 0) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const [firstError] = errors;
|
|
395
|
+
if (errors.length === 1 && firstError !== undefined) {
|
|
396
|
+
throw firstError;
|
|
397
|
+
}
|
|
398
|
+
throw new AggregateError(errors, 'Smoke harness cleanup failed.');
|
|
399
|
+
}
|
|
400
|
+
function withoutStringValue(value, removedValue) {
|
|
401
|
+
if (!Array.isArray(value)) {
|
|
402
|
+
return value;
|
|
403
|
+
}
|
|
404
|
+
return value.filter((entry) => entry !== removedValue);
|
|
405
|
+
}
|
|
406
|
+
export async function disableOpenClawMcpPortalPlugin(configPath) {
|
|
407
|
+
const config = mutableJsonRecord(await loadJsonConfigFile(configPath));
|
|
408
|
+
if (!config) {
|
|
409
|
+
throw new Error(`OpenClaw config at ${configPath} must be an object.`);
|
|
410
|
+
}
|
|
411
|
+
const plugins = mutableJsonRecord(config.plugins);
|
|
412
|
+
const pluginLoad = mutableJsonRecord(plugins?.load);
|
|
413
|
+
if (pluginLoad) {
|
|
414
|
+
pluginLoad.paths = withoutStringValue(pluginLoad.paths, defaultOpenClawMcpPortalExtensionsPath);
|
|
415
|
+
}
|
|
416
|
+
if (plugins) {
|
|
417
|
+
plugins.allow = withoutStringValue(plugins.allow, openClawMcpPortalPluginName);
|
|
418
|
+
}
|
|
419
|
+
const pluginEntries = mutableJsonRecord(plugins?.entries);
|
|
420
|
+
if (pluginEntries) {
|
|
421
|
+
delete pluginEntries[openClawMcpPortalPluginName];
|
|
422
|
+
}
|
|
423
|
+
await fs.writeFile(configPath, `${JSON.stringify(config, null, '\t')}\n`, 'utf8');
|
|
424
|
+
}
|
|
242
425
|
export async function useLocalOpenClawGatewayImagePackages(options) {
|
|
243
426
|
const gatewayProfile = options.systemConfig.imageProfiles.gateways[options.profileName];
|
|
244
427
|
if (!gatewayProfile) {
|
|
@@ -248,61 +431,169 @@ export async function useLocalOpenClawGatewayImagePackages(options) {
|
|
|
248
431
|
const baseImage = managedImageRelease.baseImages['openclaw-gateway'];
|
|
249
432
|
const dockerContextDirectory = path.join(options.projectRoot, 'vm-images', 'gateways', `${options.profileName}-local-packages`);
|
|
250
433
|
const dockerfilePath = path.join(dockerContextDirectory, 'Dockerfile');
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
await fs.rm(dockerContextDirectory, { force: true, recursive: true });
|
|
264
|
-
await fs.mkdir(dockerContextDirectory, { recursive: true });
|
|
265
|
-
await Promise.all(packages.map((packageConfig) => archivePackageDist({
|
|
266
|
-
archivePath: path.join(dockerContextDirectory, packageConfig.archiveName),
|
|
267
|
-
distDirectory: path.join(packageConfig.packageDirectory, 'dist'),
|
|
268
|
-
})));
|
|
269
|
-
const portalServerPath = path.join(options.repoRoot, 'packages', 'mcp-portal', 'dist', 'bin', 'portal-server.js');
|
|
270
|
-
await fs.access(portalServerPath);
|
|
434
|
+
const localConfigContractsTarballPath = await packLocalPackageTarball({
|
|
435
|
+
packageDirectory: path.join(options.repoRoot, 'packages', 'config-contracts'),
|
|
436
|
+
packageName: 'config-contracts',
|
|
437
|
+
});
|
|
438
|
+
const localSecretsTarballPath = await packLocalPackageTarball({
|
|
439
|
+
packageDirectory: path.join(options.repoRoot, 'packages', 'secrets'),
|
|
440
|
+
packageName: 'secrets',
|
|
441
|
+
});
|
|
442
|
+
const localGondolinAdapterTarballPath = await packLocalPackageTarball({
|
|
443
|
+
packageDirectory: path.join(options.repoRoot, 'packages', 'gondolin-adapter'),
|
|
444
|
+
packageName: 'gondolin-adapter',
|
|
445
|
+
});
|
|
271
446
|
const localMcpPortalTarballPath = await packLocalPackageTarball({
|
|
272
447
|
packageDirectory: path.join(options.repoRoot, 'packages', 'mcp-portal'),
|
|
273
448
|
packageName: 'mcp-portal',
|
|
274
449
|
});
|
|
275
|
-
const
|
|
276
|
-
packageDirectory: path.join(options.repoRoot, 'packages', '
|
|
277
|
-
packageName: '
|
|
450
|
+
const localOpenClawAgentVmPluginTarballPath = await packLocalPackageTarball({
|
|
451
|
+
packageDirectory: path.join(options.repoRoot, 'packages', 'openclaw-agent-vm-plugin'),
|
|
452
|
+
packageName: 'openclaw-agent-vm-plugin',
|
|
278
453
|
});
|
|
279
|
-
await
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
projectRoot: options.projectRoot,
|
|
283
|
-
systemConfig: options.systemConfig,
|
|
454
|
+
const localOpenClawMcpPortalPluginTarballPath = await packLocalPackageTarball({
|
|
455
|
+
packageDirectory: path.join(options.repoRoot, 'packages', 'openclaw-mcp-portal-plugin'),
|
|
456
|
+
packageName: 'openclaw-mcp-portal-plugin',
|
|
284
457
|
});
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
458
|
+
try {
|
|
459
|
+
const localPackageTarballs = [
|
|
460
|
+
{
|
|
461
|
+
archiveName: localPackageTarballArchiveName('config-contracts'),
|
|
462
|
+
sourcePath: localConfigContractsTarballPath,
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
archiveName: localPackageTarballArchiveName('secrets'),
|
|
466
|
+
sourcePath: localSecretsTarballPath,
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
archiveName: localPackageTarballArchiveName('gondolin-adapter'),
|
|
470
|
+
sourcePath: localGondolinAdapterTarballPath,
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
archiveName: localPackageTarballArchiveName('mcp-portal'),
|
|
474
|
+
sourcePath: localMcpPortalTarballPath,
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
archiveName: localPackageTarballArchiveName('openclaw-agent-vm-plugin'),
|
|
478
|
+
sourcePath: localOpenClawAgentVmPluginTarballPath,
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
archiveName: localPackageTarballArchiveName('openclaw-mcp-portal-plugin'),
|
|
482
|
+
sourcePath: localOpenClawMcpPortalPluginTarballPath,
|
|
483
|
+
},
|
|
484
|
+
];
|
|
485
|
+
await fs.rm(dockerContextDirectory, { force: true, recursive: true });
|
|
486
|
+
await fs.mkdir(dockerContextDirectory, { recursive: true });
|
|
487
|
+
await copyLocalPackageTarballsToDockerContext({
|
|
488
|
+
dockerContextDirectory,
|
|
489
|
+
tarballs: localPackageTarballs,
|
|
490
|
+
});
|
|
491
|
+
await useLocalToolVmMcpPortalPackageTarballs({
|
|
492
|
+
localConfigContractsTarballPath,
|
|
493
|
+
localMcpPortalTarballPath,
|
|
494
|
+
localSecretsTarballPath,
|
|
495
|
+
projectRoot: options.projectRoot,
|
|
496
|
+
systemConfig: options.systemConfig,
|
|
497
|
+
});
|
|
498
|
+
await fs.writeFile(dockerfilePath, [
|
|
499
|
+
`FROM ${baseImage.repository}:${baseImage.tag}`,
|
|
500
|
+
'',
|
|
501
|
+
'# Generated by the OpenClaw smoke harness from local package tarballs.',
|
|
502
|
+
...localPackageTarballs.map((tarball) => `COPY ${tarball.archiveName} /tmp/${tarball.archiveName}`),
|
|
503
|
+
'RUN mkdir -p /opt/agent-vm/local-packages && \\',
|
|
504
|
+
' npm install --omit=dev --no-audit --no-fund --prefix /opt/agent-vm/local-packages ' +
|
|
505
|
+
localPackageTarballs.map((tarball) => `/tmp/${tarball.archiveName}`).join(' ') +
|
|
506
|
+
' && \\',
|
|
507
|
+
' rm -f ' +
|
|
508
|
+
localPackageTarballs.map((tarball) => `/tmp/${tarball.archiveName}`).join(' '),
|
|
509
|
+
'RUN package_root="/opt/agent-vm/local-packages/node_modules" && \\',
|
|
510
|
+
' mkdir -p /home/openclaw/.openclaw/extensions && \\',
|
|
511
|
+
' ln -sfn "$package_root/@agent-vm/openclaw-agent-vm-plugin/dist" /home/openclaw/.openclaw/extensions/gondolin && \\',
|
|
512
|
+
' ln -sfn "$package_root/@agent-vm/openclaw-mcp-portal-plugin/dist" /home/openclaw/.openclaw/extensions/mcp-portal',
|
|
513
|
+
'',
|
|
514
|
+
].join('\n'), 'utf8');
|
|
515
|
+
gatewayProfile.dockerfile = dockerfilePath;
|
|
516
|
+
delete gatewayProfile.source;
|
|
517
|
+
}
|
|
518
|
+
finally {
|
|
519
|
+
await removeLocalPackageTarballDirectories([
|
|
520
|
+
localConfigContractsTarballPath,
|
|
521
|
+
localSecretsTarballPath,
|
|
522
|
+
localGondolinAdapterTarballPath,
|
|
523
|
+
localMcpPortalTarballPath,
|
|
524
|
+
localOpenClawAgentVmPluginTarballPath,
|
|
525
|
+
localOpenClawMcpPortalPluginTarballPath,
|
|
526
|
+
]);
|
|
527
|
+
}
|
|
303
528
|
}
|
|
304
529
|
export async function useLocalOpenClawPluginGatewayImage(options) {
|
|
305
|
-
|
|
530
|
+
const gatewayProfile = options.systemConfig.imageProfiles.gateways[options.profileName];
|
|
531
|
+
if (!gatewayProfile) {
|
|
532
|
+
throw new Error(`Gateway image profile '${options.profileName}' is not configured.`);
|
|
533
|
+
}
|
|
534
|
+
const managedImageRelease = await resolveManagedImageRelease();
|
|
535
|
+
const baseImage = managedImageRelease.baseImages['openclaw-gateway'];
|
|
536
|
+
const dockerContextDirectory = path.join(options.projectRoot, 'vm-images', 'gateways', `${options.profileName}-local-plugin`);
|
|
537
|
+
const dockerfilePath = path.join(dockerContextDirectory, 'Dockerfile');
|
|
538
|
+
const localSecretsTarballPath = await packLocalPackageTarball({
|
|
539
|
+
packageDirectory: path.join(options.repoRoot, 'packages', 'secrets'),
|
|
540
|
+
packageName: 'secrets',
|
|
541
|
+
});
|
|
542
|
+
const localGondolinAdapterTarballPath = await packLocalPackageTarball({
|
|
543
|
+
packageDirectory: path.join(options.repoRoot, 'packages', 'gondolin-adapter'),
|
|
544
|
+
packageName: 'gondolin-adapter',
|
|
545
|
+
});
|
|
546
|
+
const localOpenClawAgentVmPluginTarballPath = await packLocalPackageTarball({
|
|
547
|
+
packageDirectory: path.join(options.repoRoot, 'packages', 'openclaw-agent-vm-plugin'),
|
|
548
|
+
packageName: 'openclaw-agent-vm-plugin',
|
|
549
|
+
});
|
|
550
|
+
try {
|
|
551
|
+
const localPackageTarballs = [
|
|
552
|
+
{
|
|
553
|
+
archiveName: localPackageTarballArchiveName('secrets'),
|
|
554
|
+
sourcePath: localSecretsTarballPath,
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
archiveName: localPackageTarballArchiveName('gondolin-adapter'),
|
|
558
|
+
sourcePath: localGondolinAdapterTarballPath,
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
archiveName: localPackageTarballArchiveName('openclaw-agent-vm-plugin'),
|
|
562
|
+
sourcePath: localOpenClawAgentVmPluginTarballPath,
|
|
563
|
+
},
|
|
564
|
+
];
|
|
565
|
+
await fs.rm(dockerContextDirectory, { force: true, recursive: true });
|
|
566
|
+
await fs.mkdir(dockerContextDirectory, { recursive: true });
|
|
567
|
+
await copyLocalPackageTarballsToDockerContext({
|
|
568
|
+
dockerContextDirectory,
|
|
569
|
+
tarballs: localPackageTarballs,
|
|
570
|
+
});
|
|
571
|
+
await fs.writeFile(dockerfilePath, [
|
|
572
|
+
`FROM ${baseImage.repository}:${baseImage.tag}`,
|
|
573
|
+
'',
|
|
574
|
+
'# Generated by the OpenClaw smoke harness from local plugin package tarballs.',
|
|
575
|
+
...localPackageTarballs.map((tarball) => `COPY ${tarball.archiveName} /tmp/${tarball.archiveName}`),
|
|
576
|
+
'RUN mkdir -p /opt/agent-vm/local-packages && \\',
|
|
577
|
+
' npm install --omit=dev --no-audit --no-fund --prefix /opt/agent-vm/local-packages ' +
|
|
578
|
+
localPackageTarballs.map((tarball) => `/tmp/${tarball.archiveName}`).join(' ') +
|
|
579
|
+
' && \\',
|
|
580
|
+
' rm -f ' +
|
|
581
|
+
localPackageTarballs.map((tarball) => `/tmp/${tarball.archiveName}`).join(' '),
|
|
582
|
+
'RUN package_root="/opt/agent-vm/local-packages/node_modules" && \\',
|
|
583
|
+
' mkdir -p /home/openclaw/.openclaw/extensions && \\',
|
|
584
|
+
' ln -sfn "$package_root/@agent-vm/openclaw-agent-vm-plugin/dist" /home/openclaw/.openclaw/extensions/gondolin',
|
|
585
|
+
'',
|
|
586
|
+
].join('\n'), 'utf8');
|
|
587
|
+
gatewayProfile.dockerfile = dockerfilePath;
|
|
588
|
+
delete gatewayProfile.source;
|
|
589
|
+
}
|
|
590
|
+
finally {
|
|
591
|
+
await removeLocalPackageTarballDirectories([
|
|
592
|
+
localSecretsTarballPath,
|
|
593
|
+
localGondolinAdapterTarballPath,
|
|
594
|
+
localOpenClawAgentVmPluginTarballPath,
|
|
595
|
+
]);
|
|
596
|
+
}
|
|
306
597
|
}
|
|
307
598
|
export async function scaffoldOpenClawSmokeProject(options) {
|
|
308
599
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), options.prefix));
|
|
@@ -430,19 +721,12 @@ export async function writeOpenClawMcpPortalSmokeConfigs(options) {
|
|
|
430
721
|
},
|
|
431
722
|
},
|
|
432
723
|
schemaVersion: 1,
|
|
433
|
-
server: {
|
|
434
|
-
accessHeader: {
|
|
435
|
-
name: options.portalAccessHeaderName,
|
|
436
|
-
secret: { name: 'MCP_PORTAL_SERVER_SECRET', source: 'environment' },
|
|
437
|
-
},
|
|
438
|
-
host: '127.0.0.1',
|
|
439
|
-
port: 18790,
|
|
440
|
-
},
|
|
441
724
|
}, null, '\t')}\n`, 'utf8');
|
|
442
725
|
}
|
|
443
726
|
export async function startSmokeControllerRuntime(options) {
|
|
444
727
|
const restoreEnvironment = applySmokeEnvironment(options.secrets);
|
|
445
728
|
const secretResolver = createSmokeSecretResolver(options.secrets);
|
|
729
|
+
const tempRoot = path.dirname(path.dirname(options.startOptions.systemConfig.systemConfigPath));
|
|
446
730
|
try {
|
|
447
731
|
const runtime = await startControllerRuntime(options.startOptions, {
|
|
448
732
|
createSecretResolver: async () => secretResolver,
|
|
@@ -474,19 +758,52 @@ export async function startSmokeControllerRuntime(options) {
|
|
|
474
758
|
controllerUrl: `http://127.0.0.1:${options.startOptions.systemConfig.host.controllerPort}`,
|
|
475
759
|
runtime,
|
|
476
760
|
systemConfig: options.startOptions.systemConfig,
|
|
477
|
-
tempRoot
|
|
761
|
+
tempRoot,
|
|
478
762
|
close: async () => {
|
|
763
|
+
const cleanupErrors = [];
|
|
479
764
|
try {
|
|
480
765
|
await runtime.close();
|
|
481
766
|
}
|
|
767
|
+
catch (error) {
|
|
768
|
+
cleanupErrors.push(error);
|
|
769
|
+
}
|
|
770
|
+
try {
|
|
771
|
+
await removeSmokeDockerImagesForSystemConfig(options.startOptions.systemConfig);
|
|
772
|
+
}
|
|
773
|
+
catch (error) {
|
|
774
|
+
cleanupErrors.push(error);
|
|
775
|
+
}
|
|
776
|
+
try {
|
|
777
|
+
await removeSmokeTempRoot(tempRoot);
|
|
778
|
+
}
|
|
779
|
+
catch (error) {
|
|
780
|
+
cleanupErrors.push(error);
|
|
781
|
+
}
|
|
482
782
|
finally {
|
|
483
783
|
restoreEnvironment();
|
|
484
784
|
}
|
|
785
|
+
throwIfSmokeHarnessCleanupFailed(cleanupErrors);
|
|
485
786
|
},
|
|
486
787
|
};
|
|
487
788
|
}
|
|
488
789
|
catch (error) {
|
|
489
|
-
|
|
790
|
+
const cleanupErrors = [error];
|
|
791
|
+
try {
|
|
792
|
+
await removeSmokeDockerImagesForSystemConfig(options.startOptions.systemConfig);
|
|
793
|
+
}
|
|
794
|
+
catch (cleanupError) {
|
|
795
|
+
cleanupErrors.push(cleanupError);
|
|
796
|
+
}
|
|
797
|
+
try {
|
|
798
|
+
await removeSmokeTempRoot(tempRoot);
|
|
799
|
+
}
|
|
800
|
+
catch (cleanupError) {
|
|
801
|
+
cleanupErrors.push(cleanupError);
|
|
802
|
+
}
|
|
803
|
+
finally {
|
|
804
|
+
restoreEnvironment();
|
|
805
|
+
}
|
|
806
|
+
throwIfSmokeHarnessCleanupFailed(cleanupErrors);
|
|
490
807
|
throw error;
|
|
491
808
|
}
|
|
492
809
|
}
|