Compare commits

...

5 Commits

Author SHA1 Message Date
Nikhil Sonti
2b3d78a252 fix(openclaw): export produced file row from adapter barrel 2026-05-08 11:08:30 -07:00
Nikhil Sonti
da4be6c39d refactor(runtime): keep runtime exports common only 2026-05-08 11:06:10 -07:00
Nikhil Sonti
b4fff2716d refactor(agents): move openclaw domain into adapter folder 2026-05-08 11:04:46 -07:00
Nikhil Sonti
b4aa55f56b refactor(agents): move hermes into adapter folder 2026-05-08 11:01:54 -07:00
Nikhil Sonti
9372d4eabc refactor(agents): move claude and codex into adapter folders 2026-05-08 11:00:17 -07:00
50 changed files with 355 additions and 139 deletions

View File

@@ -30,6 +30,7 @@ import type {
AgentAdapter,
AgentDefinition,
} from '../../lib/agents/agent-types'
import type { FilePreview } from '../../lib/agents/openclaw'
import type { AgentHistoryPage, AgentStreamEvent } from '../../lib/agents/types'
import {
type AgentDefinitionWithActivity,
@@ -46,7 +47,6 @@ import {
TurnAlreadyActiveError,
UnknownAgentError,
} from '../services/agents/agent-harness-service'
import type { FilePreview } from '../services/openclaw/file-preview'
import type { Env } from '../types'
import { resolveBrowserContextPageIds } from '../utils/resolve-browser-context-page-ids'

View File

@@ -9,17 +9,17 @@
import { Hono } from 'hono'
import { stream } from 'hono/streaming'
import { logger } from '../../lib/logger'
import {
getOpenClawCliProvider,
getOpenClawService,
isUnsupportedOpenClawProviderError,
OpenClawAgentAlreadyExistsError,
OpenClawAgentNotFoundError,
OpenClawInvalidAgentNameError,
OpenClawProtectedAgentError,
OpenClawSessionNotFoundError,
} from '../services/openclaw/errors'
import { getOpenClawCliProvider } from '../services/openclaw/openclaw-cli-providers/registry'
import { isUnsupportedOpenClawProviderError } from '../services/openclaw/openclaw-provider-map'
import { getOpenClawService } from '../services/openclaw/openclaw-service'
} from '../../lib/agents/openclaw'
import { logger } from '../../lib/logger'
function getCreateAgentValidationError(body: { name?: string }): string | null {
if (!body.name?.trim()) {

View File

@@ -17,6 +17,10 @@ import { cors } from 'hono/cors'
import type { ContentfulStatusCode } from 'hono/utils/http-status'
import { HttpAgentError } from '../agent/errors'
import { INLINED_ENV } from '../env'
import {
convertOpenClawHistoryToAgentHistory,
getOpenClawService,
} from '../lib/agents/openclaw'
import { KlavisClient } from '../lib/clients/klavis/klavis-client'
import { initializeOAuth, shutdownOAuth } from '../lib/clients/oauth'
import { getDb } from '../lib/db'
@@ -46,8 +50,6 @@ import {
connectKlavisInBackground,
type KlavisProxyRef,
} from './services/klavis/strata-proxy'
import { convertOpenClawHistoryToAgentHistory } from './services/openclaw/history-mapper'
import { getOpenClawService } from './services/openclaw/openclaw-service'
import type { Env, HttpServerConfig } from './types'
import { defaultCorsConfig } from './utils/cors'
import { requireTrustedAppOrigin } from './utils/request-auth'

View File

@@ -19,13 +19,15 @@ import type {
} from '../../../lib/agents/agent-store'
import type { AgentDefinition } from '../../../lib/agents/agent-types'
import { DbAgentStore } from '../../../lib/agents/db-agent-store'
import {
getHermesProviderMapping,
writeHermesPerAgentProvider,
} from '../../../lib/agents/hermes'
import {
FileMessageQueue,
type QueuedMessage,
type QueuedMessageAttachment,
} from '../../../lib/agents/message-queue'
import { writeHermesPerAgentProvider } from '../hermes/hermes-paths'
import { getHermesProviderMapping } from '../hermes/hermes-provider-map'
export {
MessageQueueFullError,
@@ -34,6 +36,15 @@ export {
} from '../../../lib/agents/message-queue'
import { basename } from 'node:path'
import {
buildFilePreview,
detectMimeType,
type FilePreview,
type FileSnapshot,
getHostWorkspaceDir,
type ProducedFileRow,
ProducedFilesStore,
} from '../../../lib/agents/openclaw'
import type {
AgentHistoryPage,
AgentRowSnapshot,
@@ -42,17 +53,6 @@ import type {
} from '../../../lib/agents/types'
import { getOpenClawDir } from '../../../lib/browseros-dir'
import { logger } from '../../../lib/logger'
import {
buildFilePreview,
detectMimeType,
type FilePreview,
} from '../openclaw/file-preview'
import { getHostWorkspaceDir } from '../openclaw/openclaw-env'
import {
type FileSnapshot,
type ProducedFileRow,
ProducedFilesStore,
} from '../openclaw/produced-files-store'
export type AgentLiveness = 'working' | 'idle' | 'asleep' | 'error'

View File

@@ -5,12 +5,10 @@
*/
import type { AgentDefinition } from './agent-types'
import { prepareOpenClawContext } from './openclaw/prepare'
import {
prepareClaudeCodeContext,
prepareCodexContext,
prepareHermesContext,
} from './runtime'
import { prepareClaudeCodeContext } from './claude'
import { prepareCodexContext } from './codex'
import { prepareHermesContext } from './hermes'
import { prepareOpenClawContext } from './openclaw'
export interface PreparedAcpxAgentContext {
cwd: string
@@ -21,8 +19,7 @@ export interface PreparedAcpxAgentContext {
useBrowserosMcp: boolean
/**
* Hostname the agent should use to reach the BrowserOS HTTP MCP server.
* Default `127.0.0.1` is correct for host-process adapters (claude, codex,
* Phase A host-mode hermes). Container-spawned adapters override this to
* Default `127.0.0.1` is correct for host-process adapters. Container-spawned adapters override this to
* `host.containers.internal` so the URL injected into ACP newSession's
* mcpServers resolves from inside the container.
*/

View File

@@ -32,7 +32,7 @@ import type {
AgentHistoryEntry,
AgentHistoryToolCall,
} from './agent-types'
import { getHermesRuntime } from './runtime'
import { getHermesRuntime } from './hermes'
import type {
AgentHistoryPage,
AgentPromptInput,

View File

@@ -0,0 +1,14 @@
/**
* @license
* Copyright 2025 BrowserOS
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
export {
ClaudeRuntime,
type ClaudeRuntimeConfig,
type ConfigureClaudeRuntimeOptions,
configureClaudeRuntime,
getClaudeRuntime,
prepareClaudeCodeContext,
} from './runtime'

View File

@@ -15,9 +15,9 @@ import {
prepareBrowserosManagedContext,
} from '../acpx-agent-common'
import { resolveAgentRuntimePaths } from '../acpx-runtime-context'
import { HostProcessAgentRuntime } from './host-process-agent-runtime'
import { getAgentRuntimeRegistry } from './registry'
import type { RuntimeDescriptor } from './types'
import { HostProcessAgentRuntime } from '../runtime/host-process-agent-runtime'
import { getAgentRuntimeRegistry } from '../runtime/registry'
import type { RuntimeDescriptor } from '../runtime/types'
const CLAUDE_BINARY = 'claude'

View File

@@ -0,0 +1,14 @@
/**
* @license
* Copyright 2025 BrowserOS
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
export {
CodexRuntime,
type CodexRuntimeConfig,
type ConfigureCodexRuntimeOptions,
configureCodexRuntime,
getCodexRuntime,
prepareCodexContext,
} from './runtime'

View File

@@ -18,9 +18,9 @@ import {
materializeCodexHome,
resolveAgentRuntimePaths,
} from '../acpx-runtime-context'
import { HostProcessAgentRuntime } from './host-process-agent-runtime'
import { getAgentRuntimeRegistry } from './registry'
import type { RuntimeDescriptor } from './types'
import { HostProcessAgentRuntime } from '../runtime/host-process-agent-runtime'
import { getAgentRuntimeRegistry } from '../runtime/registry'
import type { RuntimeDescriptor } from '../runtime/types'
const CODEX_BINARY = 'codex'

View File

@@ -0,0 +1,25 @@
/**
* @license
* Copyright 2025 BrowserOS
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
export {
getHermesAgentHomeHostDir,
getHermesHarnessHostDir,
getHermesHostStateDir,
writeHermesPerAgentProvider,
} from './paths'
export {
getHermesProviderMapping,
type HermesProviderMapping,
isHermesSupportedProviderType,
} from './provider-map'
export {
type ConfigureHermesRuntimeOptions,
configureHermesRuntime,
getHermesRuntime,
HermesContainerRuntime,
type HermesContainerRuntimeConfig,
prepareHermesContext,
} from './runtime'

View File

@@ -15,7 +15,7 @@
import { mkdir, writeFile } from 'node:fs/promises'
import { join } from 'node:path'
import { getVmStateDir } from '../../../lib/browseros-dir'
import { getVmStateDir } from '../../browseros-dir'
/** Top-level Hermes state directory: `<browserosDir>/vm/hermes`. */
export function getHermesHostStateDir(browserosDir?: string): string {

View File

@@ -15,11 +15,6 @@ import {
HERMES_CONTAINER_NAME,
HERMES_IMAGE,
} from '@browseros/shared/constants/hermes'
import {
getHermesAgentHomeHostDir,
getHermesHarnessHostDir,
getHermesHostStateDir,
} from '../../../api/services/hermes/hermes-paths'
import { getBrowserosDir } from '../../browseros-dir'
import { ContainerCli } from '../../container/container-cli'
import { ImageLoader } from '../../container/image-loader'
@@ -46,9 +41,14 @@ import {
finishBrowserosManagedContext,
prepareBrowserosManagedContext,
} from '../acpx-agent-common'
import { ContainerAgentRuntime } from './container-agent-runtime'
import { getAgentRuntimeRegistry } from './registry'
import type { ExecSpec } from './types'
import { ContainerAgentRuntime } from '../runtime/container-agent-runtime'
import { getAgentRuntimeRegistry } from '../runtime/registry'
import type { ExecSpec } from '../runtime/types'
import {
getHermesAgentHomeHostDir,
getHermesHarnessHostDir,
getHermesHostStateDir,
} from './paths'
const HERMES_BINARY = '/opt/hermes/.venv/bin/hermes'

View File

@@ -6,17 +6,17 @@
import { cpSync, existsSync, mkdirSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { getBrowserosDir } from '../../../lib/browseros-dir'
import { ContainerCli, ImageLoader } from '../../../lib/container'
import { logger } from '../../../lib/logger'
import { getBrowserosDir } from '../../browseros-dir'
import { ContainerCli, ImageLoader } from '../../container'
import { logger } from '../../logger'
import {
getLimaHomeDir,
resolveBundledLimactl,
resolveBundledLimaTemplate,
VM_NAME,
VmRuntime,
} from '../../../lib/vm'
import { VM_TELEMETRY_EVENTS } from '../../../lib/vm/telemetry'
} from '../../vm'
import { VM_TELEMETRY_EVENTS } from '../../vm/telemetry'
import { ContainerRuntime } from './container-runtime'
const UNSUPPORTED_PLATFORM_MESSAGE =

View File

@@ -16,15 +16,11 @@ import type {
ContainerSpec,
LogFn,
WaitForContainerNameReleaseOptions,
} from '../../../lib/container'
import { isContainerNameInUse } from '../../../lib/container'
import { logger } from '../../../lib/logger'
import {
GUEST_VM_STATE,
hostPathToGuest,
type VmRuntime,
} from '../../../lib/vm'
import { ContainerNameInUseError } from '../../../lib/vm/errors'
} from '../../container'
import { isContainerNameInUse } from '../../container'
import { logger } from '../../logger'
import { GUEST_VM_STATE, hostPathToGuest, type VmRuntime } from '../../vm'
import { ContainerNameInUseError } from '../../vm/errors'
const GATEWAY_CONTAINER_HOME = '/home/node'
const GATEWAY_STATE_DIR = `${GATEWAY_CONTAINER_HOME}/.openclaw`

View File

@@ -21,12 +21,12 @@
* resulting AgentHistoryToolCall has both input and output.
*/
import { unwrapBrowserosAcpUserMessage } from '../../../lib/agents/acpx-runtime'
import { unwrapBrowserosAcpUserMessage } from '../../agents/acpx-runtime'
import type {
AgentHistoryEntry,
AgentHistoryToolCall,
} from '../../../lib/agents/agent-types'
import type { AgentHistoryPage } from '../../../lib/agents/types'
} from '../../agents/agent-types'
import type { AgentHistoryPage } from '../../agents/types'
import type {
OpenClawSessionHistory,
OpenClawSessionHistoryMessage,

View File

@@ -0,0 +1,111 @@
/**
* @license
* Copyright 2025 BrowserOS
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
export {
type AgentLiveStatus,
type AgentSessionState,
ClawSession,
} from './claw-session'
export {
ContainerRuntime,
type ContainerRuntimeConfig,
type GatewayContainerSpec,
} from './container-runtime'
export {
buildContainerRuntime,
type ContainerRuntimeFactoryInput,
migrateLegacyOpenClawDir,
} from './container-runtime-factory'
export {
OpenClawAgentAlreadyExistsError,
OpenClawAgentNotFoundError,
OpenClawInvalidAgentNameError,
OpenClawProtectedAgentError,
OpenClawSessionNotFoundError,
} from './errors'
export {
buildFilePreview,
detectMimeType,
type FilePreview,
} from './file-preview'
export { convertOpenClawHistoryToAgentHistory } from './history-mapper'
export {
type OpenClawAgentRecord,
OpenClawCliClient,
type OpenClawConfigBatchEntry,
type OpenClawSessionEntry,
} from './openclaw-cli-client'
export {
buildOpenClawCliProviderModelRef,
getOpenClawCliProvider,
OPENCLAW_CLI_PROVIDERS,
} from './openclaw-cli-providers/registry'
export type {
OpenClawCliProvider,
OpenClawCliProviderAuthStatus,
} from './openclaw-cli-providers/types'
export {
getHostWorkspaceDir,
getOpenClawStateConfigPath,
getOpenClawStateDir,
getOpenClawStateEnvPath,
isAgentWorkspaceNameSafe,
mergeEnvContent,
} from './openclaw-env'
export {
OpenClawHttpClient,
type OpenClawSessionHistory,
type OpenClawSessionHistoryEvent,
type OpenClawSessionHistoryMessage,
} from './openclaw-http-client'
export {
isUnsupportedOpenClawProviderError,
type ResolvedOpenClawProviderConfig,
resolveSupportedOpenClawProvider,
UnsupportedOpenClawProviderError,
} from './openclaw-provider-map'
export {
type BrowserOSChatHistoryAttachment,
type BrowserOSChatHistoryItem,
type BrowserOSChatHistoryReasoning,
type BrowserOSChatHistoryToolCall,
type BrowserOSOpenClawAgentSessionResponse,
type BrowserOSOpenClawSession,
configureOpenClawService,
configureVmRuntime,
getOpenClawService,
normalizeBrowserOSChatSessionKey,
type OpenClawAgentEntry,
type OpenClawControlPlaneStatus,
type OpenClawGatewayRecoveryReason,
OpenClawService,
type OpenClawServiceConfig,
type OpenClawSessionSource,
type OpenClawStatus,
type OpenClawStatusResponse,
type SetupInput,
} from './openclaw-service'
export type { OpenClawStreamEvent } from './openclaw-types'
export { prepareOpenClawContext } from './prepare'
export {
type FileSnapshot,
type FileSnapshotEntry,
type FinalizeTurnInput,
type ProducedFileRow,
ProducedFilesStore,
type ResolvedFile,
resolveSafeWorkspacePath,
} from './produced-files-store'
export {
type WorkspaceFileMetadata,
type WorkspaceFileVisitor,
walkWorkspace,
} from './produced-files-walker'
export {
type AllocateGatewayPortOptions,
allocateGatewayPort,
readPersistedGatewayPort,
} from './runtime-state'

View File

@@ -17,10 +17,10 @@ import {
OPENCLAW_IMAGE,
} from '@browseros/shared/constants/openclaw'
import { DEFAULT_PORTS } from '@browseros/shared/constants/ports'
import type { AgentStreamEvent } from '../../../lib/agents/types'
import { getOpenClawDir } from '../../../lib/browseros-dir'
import { logger } from '../../../lib/logger'
import { withProcessLock } from '../../../lib/process-lock'
import type { AgentStreamEvent } from '../../agents/types'
import { getOpenClawDir } from '../../browseros-dir'
import { logger } from '../../logger'
import { withProcessLock } from '../../process-lock'
import {
type AgentLiveStatus,
type AgentSessionState,

View File

@@ -18,13 +18,13 @@ import { randomUUID } from 'node:crypto'
import { realpath, stat } from 'node:fs/promises'
import { relative, resolve, sep } from 'node:path'
import { and, desc, eq } from 'drizzle-orm'
import { type BrowserOsDatabase, getDb } from '../../../lib/db'
import { type BrowserOsDatabase, getDb } from '../../db'
import {
agentDefinitions,
type NewProducedFileRow,
type ProducedFileRow,
producedFiles,
} from '../../../lib/db/schema'
} from '../../db/schema'
import { walkWorkspace } from './produced-files-walker'
const TURN_PROMPT_MAX_CHARS = 280

View File

@@ -5,32 +5,8 @@
*/
export type { AgentRuntime } from './agent-runtime'
export {
ClaudeRuntime,
type ClaudeRuntimeConfig,
type ConfigureClaudeRuntimeOptions,
configureClaudeRuntime,
getClaudeRuntime,
prepareClaudeCodeContext,
} from './claude-host-process-runtime'
export {
CodexRuntime,
type CodexRuntimeConfig,
type ConfigureCodexRuntimeOptions,
configureCodexRuntime,
getCodexRuntime,
prepareCodexContext,
} from './codex-host-process-runtime'
export { ContainerAgentRuntime } from './container-agent-runtime'
export { ActionNotSupportedError, RuntimeNotReadyError } from './errors'
export {
type ConfigureHermesRuntimeOptions,
configureHermesRuntime,
getHermesRuntime,
HermesContainerRuntime,
type HermesContainerRuntimeConfig,
prepareHermesContext,
} from './hermes-container-runtime'
export {
HostProcessAgentRuntime,
type HostProcessAgentRuntimeDeps,

View File

@@ -12,21 +12,18 @@ import fs from 'node:fs'
import path from 'node:path'
import { EXIT_CODES } from '@browseros/shared/constants/exit-codes'
import { createHttpServer } from './api/server'
import {
configureOpenClawService,
configureVmRuntime,
getOpenClawService,
} from './api/services/openclaw/openclaw-service'
import { CdpBackend } from './browser/backends/cdp'
import { Browser } from './browser/browser'
import type { ServerConfig } from './config'
import { INLINED_ENV } from './env'
import { configureClaudeRuntime } from './lib/agents/claude'
import { configureCodexRuntime } from './lib/agents/codex'
import { configureHermesRuntime, getHermesRuntime } from './lib/agents/hermes'
import {
configureClaudeRuntime,
configureCodexRuntime,
configureHermesRuntime,
getHermesRuntime,
} from './lib/agents/runtime'
configureOpenClawService,
configureVmRuntime,
getOpenClawService,
} from './lib/agents/openclaw'
import {
cleanOldSessions,
ensureBrowserosDir,

View File

@@ -4,8 +4,8 @@
*/
import { afterEach, describe, expect, it, mock } from 'bun:test'
import { OpenClawSessionNotFoundError } from '../../../src/api/services/openclaw/errors'
import { UnsupportedOpenClawProviderError } from '../../../src/api/services/openclaw/openclaw-provider-map'
import { OpenClawSessionNotFoundError } from '../../../src/lib/agents/openclaw/errors'
import { UnsupportedOpenClawProviderError } from '../../../src/lib/agents/openclaw/openclaw-provider-map'
describe('createOpenClawRoutes', () => {
afterEach(() => {
@@ -14,13 +14,13 @@ describe('createOpenClawRoutes', () => {
it('returns 400 for unsupported provider payloads', async () => {
const actualOpenClawService = await import(
'../../../src/api/services/openclaw/openclaw-service'
'../../../src/lib/agents/openclaw'
)
const updateProviderKeys = mock(async () => {
throw new UnsupportedOpenClawProviderError('google')
})
mock.module('../../../src/api/services/openclaw/openclaw-service', () => ({
mock.module('../../../src/lib/agents/openclaw', () => ({
...actualOpenClawService,
getOpenClawService: () =>
({
@@ -54,14 +54,14 @@ describe('createOpenClawRoutes', () => {
it('returns a non-restarting response when only the default model changes', async () => {
const actualOpenClawService = await import(
'../../../src/api/services/openclaw/openclaw-service'
'../../../src/lib/agents/openclaw'
)
const updateProviderKeys = mock(async () => ({
restarted: false,
modelUpdated: true,
}))
mock.module('../../../src/api/services/openclaw/openclaw-service', () => ({
mock.module('../../../src/lib/agents/openclaw', () => ({
...actualOpenClawService,
getOpenClawService: () =>
({
@@ -109,7 +109,7 @@ describe('createOpenClawRoutes', () => {
it('ignores role fields when creating agents', async () => {
const actualOpenClawService = await import(
'../../../src/api/services/openclaw/openclaw-service'
'../../../src/lib/agents/openclaw'
)
const createAgent = mock(async () => ({
agentId: 'research',
@@ -117,7 +117,7 @@ describe('createOpenClawRoutes', () => {
workspace: '/home/node/.openclaw/workspace-research',
}))
mock.module('../../../src/api/services/openclaw/openclaw-service', () => ({
mock.module('../../../src/lib/agents/openclaw', () => ({
...actualOpenClawService,
getOpenClawService: () =>
({
@@ -162,7 +162,7 @@ describe('createOpenClawRoutes', () => {
it('returns JSON history from the session history route and forwards query params', async () => {
const actualOpenClawService = await import(
'../../../src/api/services/openclaw/openclaw-service'
'../../../src/lib/agents/openclaw'
)
const getSessionHistory = mock(async () => ({
sessionKey: 'agent:main:main',
@@ -171,7 +171,7 @@ describe('createOpenClawRoutes', () => {
hasMore: false,
}))
mock.module('../../../src/api/services/openclaw/openclaw-service', () => ({
mock.module('../../../src/lib/agents/openclaw', () => ({
...actualOpenClawService,
getOpenClawService: () => ({ getSessionHistory }) as never,
}))
@@ -201,13 +201,13 @@ describe('createOpenClawRoutes', () => {
it('returns 404 when the service reports a missing session', async () => {
const actualOpenClawService = await import(
'../../../src/api/services/openclaw/openclaw-service'
'../../../src/lib/agents/openclaw'
)
const getSessionHistory = mock(async () => {
throw new OpenClawSessionNotFoundError('missing')
})
mock.module('../../../src/api/services/openclaw/openclaw-service', () => ({
mock.module('../../../src/lib/agents/openclaw', () => ({
...actualOpenClawService,
getOpenClawService: () => ({ getSessionHistory }) as never,
}))
@@ -227,7 +227,7 @@ describe('createOpenClawRoutes', () => {
it('streams named SSE frames when Accept: text/event-stream', async () => {
const actualOpenClawService = await import(
'../../../src/api/services/openclaw/openclaw-service'
'../../../src/lib/agents/openclaw'
)
const streamSessionHistory = mock(
async () =>
@@ -255,7 +255,7 @@ describe('createOpenClawRoutes', () => {
}),
)
mock.module('../../../src/api/services/openclaw/openclaw-service', () => ({
mock.module('../../../src/lib/agents/openclaw', () => ({
...actualOpenClawService,
getOpenClawService: () => ({ streamSessionHistory }) as never,
}))

View File

@@ -21,9 +21,9 @@ import {
unwrapBrowserosAcpUserMessage,
} from '../../../src/lib/agents/acpx-runtime'
import type { AgentDefinition } from '../../../src/lib/agents/agent-types'
import { HermesContainerRuntime } from '../../../src/lib/agents/hermes'
import {
getAgentRuntimeRegistry,
HermesContainerRuntime,
resetAgentRuntimeRegistry,
} from '../../../src/lib/agents/runtime'
import type { AgentStreamEvent } from '../../../src/lib/agents/types'

View File

@@ -10,9 +10,11 @@ import { join } from 'node:path'
import {
ClaudeRuntime,
configureClaudeRuntime,
getAgentRuntimeRegistry,
getClaudeRuntime,
prepareClaudeCodeContext,
} from '../../../../src/lib/agents/claude'
import {
getAgentRuntimeRegistry,
resetAgentRuntimeRegistry,
} from '../../../../src/lib/agents/runtime'

View File

@@ -10,9 +10,11 @@ import { join } from 'node:path'
import {
CodexRuntime,
configureCodexRuntime,
getAgentRuntimeRegistry,
getCodexRuntime,
prepareCodexContext,
} from '../../../../src/lib/agents/codex'
import {
getAgentRuntimeRegistry,
resetAgentRuntimeRegistry,
} from '../../../../src/lib/agents/runtime'

View File

@@ -0,0 +1,78 @@
/**
* @license
* Copyright 2025 BrowserOS
*/
import { describe, expect, it } from 'bun:test'
import { mkdtemp, readFile, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import {
getHermesAgentHomeHostDir,
getHermesHarnessHostDir,
getHermesHostStateDir,
getHermesProviderMapping,
writeHermesPerAgentProvider,
} from '../../../../src/lib/agents/hermes'
describe('Hermes adapter helpers', () => {
it('resolves Hermes state, harness, and per-agent home paths under vm/hermes', () => {
const root = '/tmp/browseros'
expect(getHermesHostStateDir(root)).toBe('/tmp/browseros/vm/hermes')
expect(getHermesHarnessHostDir(root)).toBe(
'/tmp/browseros/vm/hermes/harness',
)
expect(
getHermesAgentHomeHostDir({ browserosDir: root, agentId: 'agent-1' }),
).toBe('/tmp/browseros/vm/hermes/harness/agent-1/home')
})
it('writes provider config and requires a base URL for custom providers', async () => {
const browserosDir = await mkdtemp(join(tmpdir(), 'browseros-hermes-'))
try {
await writeHermesPerAgentProvider({
browserosDir,
agentId: 'agent-1',
providerId: 'custom',
envVarName: 'OPENAI_API_KEY',
apiKey: 'sk-test',
modelId: 'gpt-5.5',
baseUrl: 'https://api.openai.com/v1',
})
const home = getHermesAgentHomeHostDir({
browserosDir,
agentId: 'agent-1',
})
expect(await readFile(join(home, 'config.yaml'), 'utf8')).toContain(
'base_url: "https://api.openai.com/v1"',
)
expect(await readFile(join(home, '.env'), 'utf8')).toBe(
'OPENAI_API_KEY=sk-test\n',
)
await expect(
writeHermesPerAgentProvider({
browserosDir,
agentId: 'agent-2',
providerId: 'custom',
envVarName: 'OPENAI_API_KEY',
apiKey: 'sk-test',
modelId: 'gpt-5.5',
}),
).rejects.toThrow(/requires base_url/)
} finally {
await rm(browserosDir, { recursive: true, force: true })
}
})
it('maps BrowserOS provider types to Hermes provider config', () => {
expect(getHermesProviderMapping('anthropic')).toEqual({
hermesProvider: 'anthropic',
envVarName: 'ANTHROPIC_API_KEY',
requiresBaseUrl: false,
})
expect(getHermesProviderMapping('openai')?.defaultBaseUrl).toBe(
'https://api.openai.com/v1',
)
expect(getHermesProviderMapping('unknown')).toBeUndefined()
})
})

View File

@@ -15,9 +15,11 @@ import {
} from '../../../../../../packages/shared/src/constants/hermes'
import {
configureHermesRuntime,
getAgentRuntimeRegistry,
getHermesRuntime,
HermesContainerRuntime,
} from '../../../../src/lib/agents/hermes'
import {
getAgentRuntimeRegistry,
resetAgentRuntimeRegistry,
} from '../../../../src/lib/agents/runtime'
import type {

View File

@@ -9,7 +9,7 @@ import { dirname, join } from 'node:path'
import {
buildContainerRuntime,
migrateLegacyOpenClawDir,
} from '../../../../src/api/services/openclaw/container-runtime-factory'
} from '../../../../src/lib/agents/openclaw/container-runtime-factory'
import { logger } from '../../../../src/lib/logger'
describe('container-runtime factory', () => {

View File

@@ -8,7 +8,7 @@ import {
OPENCLAW_GATEWAY_CONTAINER_NAME,
OPENCLAW_IMAGE,
} from '@browseros/shared/constants/openclaw'
import { ContainerRuntime } from '../../../../src/api/services/openclaw/container-runtime'
import { ContainerRuntime } from '../../../../src/lib/agents/openclaw/container-runtime'
import { ContainerNameInUseError } from '../../../../src/lib/vm/errors'
const PROJECT_DIR = '/tmp/openclaw'

View File

@@ -7,8 +7,8 @@ import { describe, expect, it } from 'bun:test'
import {
cleanHistoryUserText,
convertOpenClawHistoryToAgentHistory,
} from '../../../../src/api/services/openclaw/history-mapper'
import type { OpenClawSessionHistory } from '../../../../src/api/services/openclaw/openclaw-http-client'
} from '../../../../src/lib/agents/openclaw/history-mapper'
import type { OpenClawSessionHistory } from '../../../../src/lib/agents/openclaw/openclaw-http-client'
describe('cleanHistoryUserText', () => {
it('extracts the cron payload and drops the trailer', () => {

View File

@@ -5,7 +5,7 @@
import { describe, expect, it, mock } from 'bun:test'
import { OPENCLAW_CONTAINER_HOME } from '@browseros/shared/constants/openclaw'
import { OpenClawCliClient } from '../../../../src/api/services/openclaw/openclaw-cli-client'
import { OpenClawCliClient } from '../../../../src/lib/agents/openclaw/openclaw-cli-client'
describe('OpenClawCliClient', () => {
it('passes real non-interactive onboarding flags through to the upstream cli', async () => {

View File

@@ -8,7 +8,7 @@ import {
getHostWorkspaceDir,
isAgentWorkspaceNameSafe,
mergeEnvContent,
} from '../../../../src/api/services/openclaw/openclaw-env'
} from '../../../../src/lib/agents/openclaw/openclaw-env'
describe('isAgentWorkspaceNameSafe', () => {
it('accepts plain slugs', () => {

View File

@@ -4,8 +4,8 @@
*/
import { afterEach, describe, expect, it, mock } from 'bun:test'
import { OpenClawSessionNotFoundError } from '../../../../src/api/services/openclaw/errors'
import { OpenClawHttpClient } from '../../../../src/api/services/openclaw/openclaw-http-client'
import { OpenClawSessionNotFoundError } from '../../../../src/lib/agents/openclaw/errors'
import { OpenClawHttpClient } from '../../../../src/lib/agents/openclaw/openclaw-http-client'
describe('OpenClawHttpClient', () => {
const originalFetch = globalThis.fetch

View File

@@ -15,11 +15,11 @@ import {
import {
resolveSupportedOpenClawProvider,
UnsupportedOpenClawProviderError,
} from '../../../../src/api/services/openclaw/openclaw-provider-map'
} from '../../../../src/lib/agents/openclaw/openclaw-provider-map'
import {
normalizeBrowserOSChatSessionKey,
OpenClawService,
} from '../../../../src/api/services/openclaw/openclaw-service'
} from '../../../../src/lib/agents/openclaw/openclaw-service'
type MutableOpenClawService = OpenClawService & {
openclawDir: string

View File

@@ -8,7 +8,7 @@ import { mkdtempSync } from 'node:fs'
import { mkdir, rm, symlink, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { resolveSafeWorkspacePath } from '../../../../src/api/services/openclaw/produced-files-store'
import { resolveSafeWorkspacePath } from '../../../../src/lib/agents/openclaw/produced-files-store'
describe('resolveSafeWorkspacePath', () => {
const tempDirs: string[] = []

View File

@@ -118,10 +118,10 @@ async function setupApplicationTest() {
const apiServer = await import('../src/api/server')
const browserModule = await import('../src/browser/browser')
const cdpModule = await import('../src/browser/backends/cdp')
const openclawService = await import(
'../src/api/services/openclaw/openclaw-service'
)
const runtimeModule = await import('../src/lib/agents/runtime')
const openclawService = await import('../src/lib/agents/openclaw')
const claudeModule = await import('../src/lib/agents/claude')
const codexModule = await import('../src/lib/agents/codex')
const hermesModule = await import('../src/lib/agents/hermes')
const browserosDir = await import('../src/lib/browseros-dir')
const dbModule = await import('../src/lib/db')
const identityModule = await import('../src/lib/identity')
@@ -209,16 +209,16 @@ async function setupApplicationTest() {
const hermesExecuteAction = mock(async () => {})
const fakeHermesRuntime = { executeAction: hermesExecuteAction } as never
spyOn(runtimeModule, 'configureHermesRuntime').mockImplementation(
spyOn(hermesModule, 'configureHermesRuntime').mockImplementation(
() => fakeHermesRuntime,
)
spyOn(runtimeModule, 'getHermesRuntime').mockImplementation(
spyOn(hermesModule, 'getHermesRuntime').mockImplementation(
() => fakeHermesRuntime,
)
spyOn(runtimeModule, 'configureClaudeRuntime').mockImplementation(
spyOn(claudeModule, 'configureClaudeRuntime').mockImplementation(
() => ({}) as never,
)
spyOn(runtimeModule, 'configureCodexRuntime').mockImplementation(
spyOn(codexModule, 'configureCodexRuntime').mockImplementation(
() => ({}) as never,
)