@agent-vm/agent-vm 0.0.68 → 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 +436 -58
- 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 +1 -6
- package/dist/operations/openclaw-deployment-doctor.d.ts.map +1 -1
- package/dist/operations/openclaw-deployment-doctor.js +17 -71
- 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,12 +217,210 @@ function getWorkerSmokeZone(systemConfig) {
|
|
|
185
217
|
}
|
|
186
218
|
return { ...zone, gateway: zone.gateway };
|
|
187
219
|
}
|
|
188
|
-
async function
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
220
|
+
async function packLocalPackageTarball(props) {
|
|
221
|
+
const packageJsonPath = path.join(props.packageDirectory, 'package.json');
|
|
222
|
+
await fs.access(packageJsonPath);
|
|
223
|
+
const packDirectory = await fs.mkdtemp(path.join(os.tmpdir(), `${props.packageName}-pack-`));
|
|
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);
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
await fs.rm(packDirectory, { force: true, recursive: true });
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
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) {
|
|
257
|
+
const managedImageRelease = await resolveManagedImageRelease();
|
|
258
|
+
const baseImage = managedImageRelease.baseImages['tool-vm'];
|
|
259
|
+
const toolVmProfiles = Object.entries(options.systemConfig.imageProfiles.toolVms);
|
|
260
|
+
await Promise.all(toolVmProfiles.map(async ([profileName, toolVmProfile]) => {
|
|
261
|
+
const dockerContextDirectory = path.join(options.projectRoot, 'vm-images', 'tool-vms', `${profileName}-local-mcp-portal`);
|
|
262
|
+
const dockerfilePath = path.join(dockerContextDirectory, 'Dockerfile');
|
|
263
|
+
const localConfigContractsTarballName = 'config-contracts-local.tgz';
|
|
264
|
+
const localSecretsTarballName = 'secrets-local.tgz';
|
|
265
|
+
const localTarballName = 'mcp-portal-local.tgz';
|
|
266
|
+
await fs.rm(dockerContextDirectory, { force: true, recursive: true });
|
|
267
|
+
await fs.mkdir(dockerContextDirectory, { recursive: true });
|
|
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
|
+
});
|
|
282
|
+
await fs.writeFile(dockerfilePath, [
|
|
283
|
+
`FROM ${baseImage.repository}:${baseImage.tag}`,
|
|
284
|
+
'',
|
|
285
|
+
'# Generated by the OpenClaw smoke harness from the local MCP Portal package.',
|
|
286
|
+
'COPY config-contracts-local.tgz /tmp/config-contracts-local.tgz',
|
|
287
|
+
'COPY secrets-local.tgz /tmp/secrets-local.tgz',
|
|
288
|
+
'COPY mcp-portal-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',
|
|
292
|
+
'',
|
|
293
|
+
].join('\n'), 'utf8');
|
|
294
|
+
toolVmProfile.dockerfile = dockerfilePath;
|
|
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',
|
|
193
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
|
+
]);
|
|
326
|
+
}
|
|
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');
|
|
194
424
|
}
|
|
195
425
|
export async function useLocalOpenClawGatewayImagePackages(options) {
|
|
196
426
|
const gatewayProfile = options.systemConfig.imageProfiles.gateways[options.profileName];
|
|
@@ -201,47 +431,169 @@ export async function useLocalOpenClawGatewayImagePackages(options) {
|
|
|
201
431
|
const baseImage = managedImageRelease.baseImages['openclaw-gateway'];
|
|
202
432
|
const dockerContextDirectory = path.join(options.projectRoot, 'vm-images', 'gateways', `${options.profileName}-local-packages`);
|
|
203
433
|
const dockerfilePath = path.join(dockerContextDirectory, 'Dockerfile');
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
await
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
await
|
|
225
|
-
|
|
226
|
-
'',
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
+
});
|
|
446
|
+
const localMcpPortalTarballPath = await packLocalPackageTarball({
|
|
447
|
+
packageDirectory: path.join(options.repoRoot, 'packages', 'mcp-portal'),
|
|
448
|
+
packageName: 'mcp-portal',
|
|
449
|
+
});
|
|
450
|
+
const localOpenClawAgentVmPluginTarballPath = await packLocalPackageTarball({
|
|
451
|
+
packageDirectory: path.join(options.repoRoot, 'packages', 'openclaw-agent-vm-plugin'),
|
|
452
|
+
packageName: 'openclaw-agent-vm-plugin',
|
|
453
|
+
});
|
|
454
|
+
const localOpenClawMcpPortalPluginTarballPath = await packLocalPackageTarball({
|
|
455
|
+
packageDirectory: path.join(options.repoRoot, 'packages', 'openclaw-mcp-portal-plugin'),
|
|
456
|
+
packageName: 'openclaw-mcp-portal-plugin',
|
|
457
|
+
});
|
|
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
|
+
}
|
|
242
528
|
}
|
|
243
529
|
export async function useLocalOpenClawPluginGatewayImage(options) {
|
|
244
|
-
|
|
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
|
+
}
|
|
245
597
|
}
|
|
246
598
|
export async function scaffoldOpenClawSmokeProject(options) {
|
|
247
599
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), options.prefix));
|
|
@@ -369,19 +721,12 @@ export async function writeOpenClawMcpPortalSmokeConfigs(options) {
|
|
|
369
721
|
},
|
|
370
722
|
},
|
|
371
723
|
schemaVersion: 1,
|
|
372
|
-
server: {
|
|
373
|
-
accessHeader: {
|
|
374
|
-
name: options.portalAccessHeaderName,
|
|
375
|
-
secret: { name: 'MCP_PORTAL_SERVER_SECRET', source: 'environment' },
|
|
376
|
-
},
|
|
377
|
-
host: '127.0.0.1',
|
|
378
|
-
port: 18790,
|
|
379
|
-
},
|
|
380
724
|
}, null, '\t')}\n`, 'utf8');
|
|
381
725
|
}
|
|
382
726
|
export async function startSmokeControllerRuntime(options) {
|
|
383
727
|
const restoreEnvironment = applySmokeEnvironment(options.secrets);
|
|
384
728
|
const secretResolver = createSmokeSecretResolver(options.secrets);
|
|
729
|
+
const tempRoot = path.dirname(path.dirname(options.startOptions.systemConfig.systemConfigPath));
|
|
385
730
|
try {
|
|
386
731
|
const runtime = await startControllerRuntime(options.startOptions, {
|
|
387
732
|
createSecretResolver: async () => secretResolver,
|
|
@@ -413,19 +758,52 @@ export async function startSmokeControllerRuntime(options) {
|
|
|
413
758
|
controllerUrl: `http://127.0.0.1:${options.startOptions.systemConfig.host.controllerPort}`,
|
|
414
759
|
runtime,
|
|
415
760
|
systemConfig: options.startOptions.systemConfig,
|
|
416
|
-
tempRoot
|
|
761
|
+
tempRoot,
|
|
417
762
|
close: async () => {
|
|
763
|
+
const cleanupErrors = [];
|
|
418
764
|
try {
|
|
419
765
|
await runtime.close();
|
|
420
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
|
+
}
|
|
421
782
|
finally {
|
|
422
783
|
restoreEnvironment();
|
|
423
784
|
}
|
|
785
|
+
throwIfSmokeHarnessCleanupFailed(cleanupErrors);
|
|
424
786
|
},
|
|
425
787
|
};
|
|
426
788
|
}
|
|
427
789
|
catch (error) {
|
|
428
|
-
|
|
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);
|
|
429
807
|
throw error;
|
|
430
808
|
}
|
|
431
809
|
}
|