mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-13 23:53:25 +00:00
Compare commits
3 Commits
fix/patch-
...
fix/opencl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63055a8ea2 | ||
|
|
40cf3f56e9 | ||
|
|
13c069631a |
@@ -1,4 +1,4 @@
|
||||
name: build-agent
|
||||
name: Publish VM Agent Cache
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -16,7 +16,7 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "packages/browseros-agent/packages/build-tools/**"
|
||||
- ".github/workflows/build-agent.yml"
|
||||
- ".github/workflows/publish-vm-agent-cache.yml"
|
||||
|
||||
env:
|
||||
BUN_VERSION: "1.3.6"
|
||||
@@ -48,6 +48,8 @@ jobs:
|
||||
include:
|
||||
- arch: arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
- arch: x64
|
||||
runner: ubuntu-24.04
|
||||
runs-on: ${{ matrix.runner }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -74,7 +76,15 @@ jobs:
|
||||
|
||||
smoke:
|
||||
needs: build
|
||||
runs-on: ubuntu-24.04-arm
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
- arch: x64
|
||||
runner: ubuntu-24.04
|
||||
runs-on: ${{ matrix.runner }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
@@ -82,7 +92,7 @@ jobs:
|
||||
bun-version: ${{ env.BUN_VERSION }}
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: tarball-${{ inputs.agent || 'openclaw' }}-arm64
|
||||
name: tarball-${{ inputs.agent || 'openclaw' }}-${{ matrix.arch }}
|
||||
path: dist/images
|
||||
- name: Install podman
|
||||
run: |
|
||||
@@ -96,12 +106,12 @@ jobs:
|
||||
AGENT: ${{ inputs.agent || 'openclaw' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tarball="$(find "$GITHUB_WORKSPACE/dist/images" -name "${AGENT}-*-arm64.tar.gz" -print -quit)"
|
||||
tarball="$(find "$GITHUB_WORKSPACE/dist/images" -name "${AGENT}-*-${{ matrix.arch }}.tar.gz" -print -quit)"
|
||||
if [ -z "$tarball" ]; then
|
||||
echo "missing arm64 tarball artifact for ${AGENT}" >&2
|
||||
echo "missing ${{ matrix.arch }} tarball artifact for ${AGENT}" >&2
|
||||
exit 1
|
||||
fi
|
||||
bun run smoke:tarball -- --agent "$AGENT" --arch arm64 --tarball "$tarball"
|
||||
bun run smoke:tarball -- --agent "$AGENT" --arch "${{ matrix.arch }}" --tarball "$tarball"
|
||||
|
||||
publish:
|
||||
needs: [build, smoke]
|
||||
@@ -123,15 +123,27 @@ class DeferredImageLoader {
|
||||
) {}
|
||||
|
||||
async ensureImageLoaded(ref: string, onLog?: (msg: string) => void) {
|
||||
const loader = await this.buildLoader()
|
||||
await loader.ensureImageLoaded(ref, onLog)
|
||||
}
|
||||
|
||||
async ensureAgentImageLoaded(
|
||||
name: string,
|
||||
onLog?: (msg: string) => void,
|
||||
): Promise<string> {
|
||||
const loader = await this.buildLoader()
|
||||
return loader.ensureAgentImageLoaded(name, onLog)
|
||||
}
|
||||
|
||||
private async buildLoader(): Promise<ImageLoader> {
|
||||
await this.ensureCacheSynced()
|
||||
const manifest = await readCachedManifest(this.browserosRoot)
|
||||
const loader = new ImageLoader(
|
||||
return new ImageLoader(
|
||||
this.shell,
|
||||
manifest,
|
||||
detectArch(),
|
||||
this.browserosRoot,
|
||||
)
|
||||
await loader.ensureImageLoaded(ref, onLog)
|
||||
}
|
||||
|
||||
private async ensureCacheSynced(): Promise<void> {
|
||||
@@ -151,7 +163,10 @@ class UnsupportedPlatformTestRuntime extends ContainerRuntime {
|
||||
super({
|
||||
vm: {} as VmRuntime,
|
||||
shell: {} as ContainerCli,
|
||||
loader: { ensureImageLoaded: rejectUnsupportedPlatform },
|
||||
loader: {
|
||||
ensureImageLoaded: rejectUnsupportedPlatform,
|
||||
ensureAgentImageLoaded: rejectUnsupportedPlatform,
|
||||
},
|
||||
projectDir,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
OPENCLAW_AGENT_NAME,
|
||||
OPENCLAW_GATEWAY_CONTAINER_NAME,
|
||||
OPENCLAW_GATEWAY_CONTAINER_PORT,
|
||||
} from '@browseros/shared/constants/openclaw'
|
||||
@@ -39,7 +40,6 @@ const GATEWAY_PATH = [
|
||||
].join(':')
|
||||
|
||||
export type GatewayContainerSpec = {
|
||||
image: string
|
||||
hostPort: number
|
||||
hostHome: string
|
||||
envFilePath: string
|
||||
@@ -50,7 +50,10 @@ export type GatewayContainerSpec = {
|
||||
export interface ContainerRuntimeConfig {
|
||||
vm: VmRuntime
|
||||
shell: ContainerCli
|
||||
loader: { ensureImageLoaded(ref: string, onLog?: LogFn): Promise<void> }
|
||||
loader: {
|
||||
ensureImageLoaded(ref: string, onLog?: LogFn): Promise<void>
|
||||
ensureAgentImageLoaded(name: string, onLog?: LogFn): Promise<string>
|
||||
}
|
||||
projectDir: string
|
||||
}
|
||||
|
||||
@@ -59,6 +62,7 @@ export class ContainerRuntime {
|
||||
private readonly shell: ContainerCli
|
||||
private readonly loader: {
|
||||
ensureImageLoaded(ref: string, onLog?: LogFn): Promise<void>
|
||||
ensureAgentImageLoaded(name: string, onLog?: LogFn): Promise<string>
|
||||
}
|
||||
private readonly projectDir: string
|
||||
|
||||
@@ -96,8 +100,8 @@ export class ContainerRuntime {
|
||||
onLog?: LogFn,
|
||||
): Promise<void> {
|
||||
await this.removeGatewayContainer(onLog)
|
||||
await this.loader.ensureImageLoaded(input.image, onLog)
|
||||
const container = await this.buildGatewayContainerSpec(input)
|
||||
const image = await this.ensureGatewayImageLoaded(onLog)
|
||||
const container = await this.buildGatewayContainerSpec(input, image)
|
||||
await this.shell.createContainer(container, onLog)
|
||||
await this.shell.startContainer(container.name)
|
||||
}
|
||||
@@ -183,7 +187,7 @@ export class ContainerRuntime {
|
||||
): Promise<number> {
|
||||
const setupContainerName = `${OPENCLAW_GATEWAY_CONTAINER_NAME}-setup`
|
||||
await this.shell.removeContainer(setupContainerName, { force: true }, onLog)
|
||||
await this.loader.ensureImageLoaded(spec.image, onLog)
|
||||
const image = await this.ensureGatewayImageLoaded(onLog)
|
||||
const setupArgs = command[0] === 'node' ? command.slice(1) : command
|
||||
const createResult = await this.shell.runCommand(
|
||||
[
|
||||
@@ -191,7 +195,7 @@ export class ContainerRuntime {
|
||||
'--name',
|
||||
setupContainerName,
|
||||
...(await this.buildGatewayRunArgs(spec)),
|
||||
spec.image,
|
||||
image,
|
||||
'node',
|
||||
...setupArgs,
|
||||
],
|
||||
@@ -235,10 +239,11 @@ export class ContainerRuntime {
|
||||
|
||||
private async buildGatewayContainerSpec(
|
||||
input: GatewayContainerSpec,
|
||||
image: string,
|
||||
): Promise<ContainerSpec> {
|
||||
return {
|
||||
name: OPENCLAW_GATEWAY_CONTAINER_NAME,
|
||||
image: input.image,
|
||||
image,
|
||||
restart: 'unless-stopped',
|
||||
ports: [
|
||||
{
|
||||
@@ -290,6 +295,16 @@ export class ContainerRuntime {
|
||||
return `host.containers.internal:${await this.vm.getDefaultGateway()}`
|
||||
}
|
||||
|
||||
private async ensureGatewayImageLoaded(onLog?: LogFn): Promise<string> {
|
||||
// Local image testing can bypass the synced VM manifest with OPENCLAW_IMAGE.
|
||||
const override = process.env.OPENCLAW_IMAGE?.trim()
|
||||
if (override) {
|
||||
await this.loader.ensureImageLoaded(override, onLog)
|
||||
return override
|
||||
}
|
||||
return this.loader.ensureAgentImageLoaded(OPENCLAW_AGENT_NAME, onLog)
|
||||
}
|
||||
|
||||
private buildGatewayEnv(input: GatewayContainerSpec): Record<string, string> {
|
||||
return {
|
||||
HOME: GATEWAY_CONTAINER_HOME,
|
||||
|
||||
@@ -1330,14 +1330,8 @@ export class OpenClawService {
|
||||
await writeFile(envPath, '', { mode: 0o600 })
|
||||
}
|
||||
|
||||
// Pin away from latest because newer OpenClaw releases regress OpenRouter chat streams.
|
||||
private getGatewayImage(): string {
|
||||
return process.env.OPENCLAW_IMAGE || 'ghcr.io/openclaw/openclaw:2026.4.12'
|
||||
}
|
||||
|
||||
private buildGatewayRuntimeSpec(): GatewayContainerSpec {
|
||||
return {
|
||||
image: this.getGatewayImage(),
|
||||
hostPort: this.hostPort,
|
||||
hostHome: this.openclawDir,
|
||||
envFilePath: this.getStateEnvPath(),
|
||||
|
||||
@@ -757,6 +757,8 @@ function resolveOpenclawAcpCommand(
|
||||
`LIMA_HOME=${limaHome}`,
|
||||
limactl,
|
||||
'shell',
|
||||
'--workdir',
|
||||
'/',
|
||||
vm,
|
||||
'--',
|
||||
'nerdctl',
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { basename, join } from 'node:path'
|
||||
import { ContainerCliError, ImageLoadError } from '../vm/errors'
|
||||
import type { VmManifest } from '../vm/manifest'
|
||||
import type { VmAgentTarball, VmManifest } from '../vm/manifest'
|
||||
import type { Arch } from '../vm/paths'
|
||||
import { getImageCacheDir, hostPathToGuest } from '../vm/paths'
|
||||
import type { ContainerCli } from './container-cli'
|
||||
@@ -24,6 +24,28 @@ export class ImageLoader {
|
||||
if (await this.cli.imageExists(ref)) return
|
||||
|
||||
const tarball = this.resolveTarball(ref)
|
||||
await this.loadResolvedTarball(ref, tarball, onLog)
|
||||
}
|
||||
|
||||
/** Load an agent tarball from the VM cache and return its local image ref. */
|
||||
async ensureAgentImageLoaded(name: string, onLog?: LogFn): Promise<string> {
|
||||
const agent = this.resolveAgent(name)
|
||||
const ref = `${agent.image}:${agent.version}`
|
||||
if (await this.cli.imageExists(ref)) return ref
|
||||
|
||||
const tarball = agent.tarballs[this.arch]
|
||||
if (!tarball) {
|
||||
throw new ImageLoadError(ref, `no ${this.arch} tarball in manifest`)
|
||||
}
|
||||
await this.loadResolvedTarball(ref, tarball, onLog)
|
||||
return ref
|
||||
}
|
||||
|
||||
private async loadResolvedTarball(
|
||||
ref: string,
|
||||
tarball: VmAgentTarball,
|
||||
onLog?: LogFn,
|
||||
): Promise<void> {
|
||||
const hostPath = join(
|
||||
getImageCacheDir(this.browserosRoot),
|
||||
basename(tarball.key),
|
||||
@@ -47,9 +69,7 @@ export class ImageLoader {
|
||||
}
|
||||
}
|
||||
|
||||
private resolveTarball(
|
||||
ref: string,
|
||||
): VmManifest['agents'][string]['tarballs'][Arch] {
|
||||
private resolveTarball(ref: string): VmAgentTarball {
|
||||
for (const agent of Object.values(this.manifest.agents)) {
|
||||
if (`${agent.image}:${agent.version}` !== ref) continue
|
||||
const tarball = agent.tarballs[this.arch]
|
||||
@@ -61,4 +81,10 @@ export class ImageLoader {
|
||||
|
||||
throw new ImageLoadError(ref, `no agent in manifest matches ${ref}`)
|
||||
}
|
||||
|
||||
private resolveAgent(name: string): VmManifest['agents'][string] {
|
||||
const agent = this.manifest.agents[name]
|
||||
if (!agent) throw new ImageLoadError(name, `no agent in manifest: ${name}`)
|
||||
return agent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ export interface VmManifest {
|
||||
agents: Record<string, VmAgentEntry>
|
||||
}
|
||||
|
||||
export type VmAgentTarball = VmArtifact
|
||||
export type VersionComparison = 'same' | 'upgrade' | 'downgrade' | 'fresh'
|
||||
|
||||
export async function readCachedManifest(
|
||||
@@ -78,7 +79,7 @@ export function agentForArch(
|
||||
): {
|
||||
image: string
|
||||
version: string
|
||||
tarball: VmManifest['agents'][string]['tarballs'][Arch]
|
||||
tarball: VmAgentTarball
|
||||
} {
|
||||
const agent = manifest.agents[name]
|
||||
if (!agent) throw new Error(`missing agent in VM manifest: ${name}`)
|
||||
|
||||
@@ -8,8 +8,8 @@ import { OPENCLAW_GATEWAY_CONTAINER_NAME } from '@browseros/shared/constants/ope
|
||||
import { ContainerRuntime } from '../../../../src/api/services/openclaw/container-runtime'
|
||||
|
||||
const PROJECT_DIR = '/tmp/openclaw'
|
||||
const GATEWAY_IMAGE_REF = 'ghcr.io/openclaw/openclaw:2026.4.12'
|
||||
const defaultSpec = {
|
||||
image: 'ghcr.io/openclaw/openclaw:2026.4.12',
|
||||
hostPort: 18789,
|
||||
hostHome: '/Users/me/.browseros/vm/openclaw',
|
||||
envFilePath: '/Users/me/.browseros/vm/openclaw/.openclaw/.env',
|
||||
@@ -34,14 +34,14 @@ describe('ContainerRuntime', () => {
|
||||
{ force: true },
|
||||
undefined,
|
||||
)
|
||||
expect(deps.loader.ensureImageLoaded).toHaveBeenCalledWith(
|
||||
defaultSpec.image,
|
||||
expect(deps.loader.ensureAgentImageLoaded).toHaveBeenCalledWith(
|
||||
'openclaw',
|
||||
undefined,
|
||||
)
|
||||
expect(deps.shell.createContainer).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: OPENCLAW_GATEWAY_CONTAINER_NAME,
|
||||
image: defaultSpec.image,
|
||||
image: GATEWAY_IMAGE_REF,
|
||||
restart: 'unless-stopped',
|
||||
ports: [
|
||||
{
|
||||
@@ -66,6 +66,35 @@ describe('ContainerRuntime', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('uses OPENCLAW_IMAGE as a direct image override', async () => {
|
||||
const previous = process.env.OPENCLAW_IMAGE
|
||||
process.env.OPENCLAW_IMAGE = 'localhost/openclaw:test'
|
||||
const deps = createDeps()
|
||||
const runtime = new ContainerRuntime({
|
||||
vm: deps.vm,
|
||||
shell: deps.shell,
|
||||
loader: deps.loader,
|
||||
projectDir: PROJECT_DIR,
|
||||
})
|
||||
|
||||
try {
|
||||
await runtime.startGateway(defaultSpec)
|
||||
} finally {
|
||||
if (previous === undefined) delete process.env.OPENCLAW_IMAGE
|
||||
else process.env.OPENCLAW_IMAGE = previous
|
||||
}
|
||||
|
||||
expect(deps.loader.ensureImageLoaded).toHaveBeenCalledWith(
|
||||
'localhost/openclaw:test',
|
||||
undefined,
|
||||
)
|
||||
expect(deps.loader.ensureAgentImageLoaded).not.toHaveBeenCalled()
|
||||
expect(deps.shell.createContainer).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ image: 'localhost/openclaw:test' }),
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
|
||||
it('delegates ensureReady and stopVm to VmRuntime', async () => {
|
||||
const deps = createDeps()
|
||||
const runtime = new ContainerRuntime({
|
||||
@@ -108,6 +137,7 @@ describe('ContainerRuntime', () => {
|
||||
'/mnt/browseros/vm/openclaw:/home/node',
|
||||
'--add-host',
|
||||
'host.containers.internal:192.168.5.2',
|
||||
GATEWAY_IMAGE_REF,
|
||||
]),
|
||||
undefined,
|
||||
)
|
||||
@@ -171,6 +201,7 @@ function createDeps() {
|
||||
},
|
||||
loader: {
|
||||
ensureImageLoaded: mock(async () => {}),
|
||||
ensureAgentImageLoaded: mock(async () => GATEWAY_IMAGE_REF),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,7 +315,6 @@ describe('OpenClawService', () => {
|
||||
expect(steps).toEqual(['onboard', 'batch', 'validate', 'start', 'ready'])
|
||||
expect(startGateway).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
image: 'ghcr.io/openclaw/openclaw:2026.4.12',
|
||||
hostPort: expect.any(Number),
|
||||
hostHome: tempDir,
|
||||
envFilePath: join(tempDir, '.openclaw', '.env'),
|
||||
@@ -323,6 +322,7 @@ describe('OpenClawService', () => {
|
||||
}),
|
||||
expect.any(Function),
|
||||
)
|
||||
expect(startGateway.mock.calls[0]?.[0]).not.toHaveProperty('image')
|
||||
expect(restartGateway).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -610,7 +610,6 @@ describe('OpenClawService', () => {
|
||||
expect(ensureReady).toHaveBeenCalledTimes(1)
|
||||
expect(startGateway).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
image: 'ghcr.io/openclaw/openclaw:2026.4.12',
|
||||
hostPort: expect.any(Number),
|
||||
hostHome: tempDir,
|
||||
envFilePath: join(tempDir, '.openclaw', '.env'),
|
||||
@@ -753,7 +752,6 @@ describe('OpenClawService', () => {
|
||||
expect(ensureReady).toHaveBeenCalledTimes(1)
|
||||
expect(restartGateway).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
image: 'ghcr.io/openclaw/openclaw:2026.4.12',
|
||||
hostPort: expect.any(Number),
|
||||
hostHome: tempDir,
|
||||
envFilePath: join(tempDir, '.openclaw', '.env'),
|
||||
@@ -962,7 +960,6 @@ describe('OpenClawService', () => {
|
||||
expect(ensureReady).toHaveBeenCalledTimes(1)
|
||||
expect(startGateway).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
image: 'ghcr.io/openclaw/openclaw:2026.4.12',
|
||||
hostPort: expect.any(Number),
|
||||
hostHome: tempDir,
|
||||
envFilePath: join(tempDir, '.openclaw', '.env'),
|
||||
|
||||
@@ -512,7 +512,9 @@ open <example.com>
|
||||
const runtimeOptions = calls[0]?.input as AcpRuntimeOptions
|
||||
const command = runtimeOptions.agentRegistry.resolve('openclaw')
|
||||
expect(command).toContain('env LIMA_HOME=/Users/dev/.browseros-dev/lima')
|
||||
expect(command).toContain('/opt/homebrew/bin/limactl shell browseros-vm --')
|
||||
expect(command).toContain(
|
||||
'/opt/homebrew/bin/limactl shell --workdir / browseros-vm --',
|
||||
)
|
||||
expect(command).toContain(
|
||||
'nerdctl exec -i -e OPENCLAW_HIDE_BANNER=1 -e OPENCLAW_SUPPRESS_NOTES=1 browseros-openclaw-openclaw-gateway-1',
|
||||
)
|
||||
|
||||
@@ -62,6 +62,78 @@ describe('ImageLoader', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('loads an agent image by manifest name and returns its image ref', async () => {
|
||||
const cli = new FakeContainerCli([false, true])
|
||||
const loader = new ImageLoader(cli as never, manifest, 'arm64')
|
||||
|
||||
await expect(loader.ensureAgentImageLoaded('openclaw')).resolves.toBe(
|
||||
'ghcr.io/openclaw/openclaw:2026.4.12',
|
||||
)
|
||||
|
||||
expect(cli.loadCalls).toEqual([
|
||||
'/mnt/browseros/cache/images/openclaw-2026.4.12-arm64.tar.gz',
|
||||
])
|
||||
expect(cli.existsCalls).toEqual([
|
||||
'ghcr.io/openclaw/openclaw:2026.4.12',
|
||||
'ghcr.io/openclaw/openclaw:2026.4.12',
|
||||
])
|
||||
})
|
||||
|
||||
it('returns an agent image ref without loading when already cached', async () => {
|
||||
const cli = new FakeContainerCli([true])
|
||||
const loader = new ImageLoader(cli as never, manifest, 'arm64')
|
||||
|
||||
await expect(loader.ensureAgentImageLoaded('openclaw')).resolves.toBe(
|
||||
'ghcr.io/openclaw/openclaw:2026.4.12',
|
||||
)
|
||||
|
||||
expect(cli.loadCalls).toEqual([])
|
||||
expect(cli.existsCalls).toEqual(['ghcr.io/openclaw/openclaw:2026.4.12'])
|
||||
})
|
||||
|
||||
it('throws ImageLoadError when the agent name is absent from the manifest', async () => {
|
||||
const cli = new FakeContainerCli([])
|
||||
const loader = new ImageLoader(cli as never, manifest, 'arm64')
|
||||
|
||||
const error = await loader
|
||||
.ensureAgentImageLoaded('missing')
|
||||
.catch((err) => err)
|
||||
|
||||
expect(error).toBeInstanceOf(ImageLoadError)
|
||||
expect(error.message).toContain('no agent in manifest: missing')
|
||||
expect(cli.existsCalls).toEqual([])
|
||||
expect(cli.loadCalls).toEqual([])
|
||||
})
|
||||
|
||||
it('throws ImageLoadError when the manifest lacks a tarball for the arch', async () => {
|
||||
const missingArchManifest = {
|
||||
...manifest,
|
||||
agents: {
|
||||
openclaw: {
|
||||
image: 'ghcr.io/openclaw/openclaw',
|
||||
version: '2026.4.12',
|
||||
tarballs: {
|
||||
arm64: {
|
||||
key: 'vm/images/openclaw-2026.4.12-arm64.tar.gz',
|
||||
sha256: 'agent-arm',
|
||||
sizeBytes: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as VmManifest
|
||||
const cli = new FakeContainerCli([false])
|
||||
const loader = new ImageLoader(cli as never, missingArchManifest, 'x64')
|
||||
|
||||
const error = await loader
|
||||
.ensureAgentImageLoaded('openclaw')
|
||||
.catch((err) => err)
|
||||
|
||||
expect(error).toBeInstanceOf(ImageLoadError)
|
||||
expect(error.message).toContain('no x64 tarball in manifest')
|
||||
expect(cli.loadCalls).toEqual([])
|
||||
})
|
||||
|
||||
it('resolves image tarballs against the configured BrowserOS root', async () => {
|
||||
const cli = new FakeContainerCli([false, true])
|
||||
const browserosRoot = '/tmp/browseros-custom-root'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export const OPENCLAW_AGENT_NAME = 'openclaw'
|
||||
export const OPENCLAW_GATEWAY_CONTAINER_PORT = 18789
|
||||
export const OPENCLAW_CONTAINER_HOME = '/home/node/.openclaw'
|
||||
export const OPENCLAW_COMPOSE_PROJECT_NAME = 'browseros-openclaw'
|
||||
|
||||
Reference in New Issue
Block a user