mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-18 02:57:47 +00:00
Compare commits
1 Commits
clean-herm
...
apr15-open
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc40b2c133 |
@@ -141,6 +141,13 @@ export class ContainerRuntime {
|
||||
)
|
||||
}
|
||||
|
||||
tailGatewayLogs(onLine: LogFn): () => void {
|
||||
return this.podman.tailContainerLogs(
|
||||
OPENCLAW_GATEWAY_CONTAINER_NAME,
|
||||
onLine,
|
||||
)
|
||||
}
|
||||
|
||||
private async compose(args: string[], onLog?: LogFn): Promise<number> {
|
||||
const lines: string[] = []
|
||||
const code = await this.podman.runCommand(['compose', ...args], {
|
||||
|
||||
@@ -65,6 +65,21 @@ function hasBuiltinProvider(providerType?: string): providerType is string {
|
||||
return !!providerType && providerType in PROVIDER_ENV_MAP
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenRouter's public slugs use dots for version numbers
|
||||
* (e.g. `anthropic/claude-haiku-4.5`), but openclaw's model registry expects
|
||||
* dashes (`claude-haiku-4-5`). Passing the dotted form makes openclaw fail
|
||||
* the registry lookup silently and the agent turn completes with zero
|
||||
* payloads. Rewrite dots to dashes for openrouter model ids only.
|
||||
*/
|
||||
function normalizeBuiltinModelId(
|
||||
providerType: string,
|
||||
modelId: string,
|
||||
): string {
|
||||
if (providerType !== 'openrouter') return modelId
|
||||
return modelId.replace(/\./g, '-')
|
||||
}
|
||||
|
||||
export function deriveOpenClawProviderId(providerInput: {
|
||||
providerType?: string
|
||||
providerName?: string
|
||||
@@ -102,10 +117,14 @@ export function resolveProviderConfig(
|
||||
providerKeys[PROVIDER_ENV_MAP[input.providerType]] = input.apiKey
|
||||
}
|
||||
|
||||
const normalizedModelId = input.modelId
|
||||
? normalizeBuiltinModelId(input.providerType, input.modelId)
|
||||
: undefined
|
||||
|
||||
return {
|
||||
providerKeys,
|
||||
model: input.modelId
|
||||
? `${input.providerType}/${input.modelId}`
|
||||
model: normalizedModelId
|
||||
? `${input.providerType}/${normalizedModelId}`
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
@@ -222,6 +241,10 @@ export function buildBootstrapConfig(
|
||||
config.models = provider.models
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
config.logging = { level: 'debug', consoleLevel: 'debug' }
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@ export class OpenClawService {
|
||||
private lastGatewayError: string | null = null
|
||||
private lastRecoveryReason: OpenClawGatewayRecoveryReason | null = null
|
||||
private gatewayReconnectPromise: Promise<void> | null = null
|
||||
private stopLogTail: (() => void) | null = null
|
||||
|
||||
constructor(browserosServerPort?: number) {
|
||||
this.openclawDir = getOpenClawDir()
|
||||
@@ -174,6 +175,7 @@ export class OpenClawService {
|
||||
|
||||
logProgress('Starting OpenClaw gateway...')
|
||||
await this.runtime.composeUp(logProgress)
|
||||
this.startGatewayLogTail()
|
||||
|
||||
logProgress('Waiting for gateway readiness...')
|
||||
const ready = await this.runtime.waitForReady(this.port, READY_TIMEOUT_MS)
|
||||
@@ -218,9 +220,11 @@ export class OpenClawService {
|
||||
|
||||
logProgress('Loading gateway auth token...')
|
||||
await this.loadTokenFromEnv()
|
||||
await this.ensureDevLoggingInConfig()
|
||||
await this.runtime.ensureReady(logProgress)
|
||||
logProgress('Starting OpenClaw gateway...')
|
||||
await this.runtime.composeUp(logProgress)
|
||||
this.startGatewayLogTail()
|
||||
|
||||
logProgress('Waiting for gateway readiness...')
|
||||
const ready = await this.runtime.waitForReady(this.port, READY_TIMEOUT_MS)
|
||||
@@ -237,6 +241,7 @@ export class OpenClawService {
|
||||
|
||||
async stop(): Promise<void> {
|
||||
this.disconnectGateway()
|
||||
this.stopGatewayLogTail()
|
||||
await this.runtime.composeStop()
|
||||
logger.info('OpenClaw container stopped')
|
||||
}
|
||||
@@ -245,10 +250,13 @@ export class OpenClawService {
|
||||
const logProgress = this.createProgressLogger(onLog)
|
||||
|
||||
this.disconnectGateway()
|
||||
this.stopGatewayLogTail()
|
||||
logProgress('Loading gateway auth token...')
|
||||
await this.loadTokenFromEnv()
|
||||
await this.ensureDevLoggingInConfig()
|
||||
logProgress('Restarting OpenClaw gateway...')
|
||||
await this.runtime.composeRestart(logProgress)
|
||||
this.startGatewayLogTail()
|
||||
|
||||
logProgress('Waiting for gateway readiness...')
|
||||
const ready = await this.runtime.waitForReady(this.port, READY_TIMEOUT_MS)
|
||||
@@ -287,6 +295,7 @@ export class OpenClawService {
|
||||
|
||||
async shutdown(): Promise<void> {
|
||||
this.disconnectGateway()
|
||||
this.stopGatewayLogTail()
|
||||
try {
|
||||
await this.runtime.composeStop()
|
||||
} catch {
|
||||
@@ -797,6 +806,52 @@ export class OpenClawService {
|
||||
await writeFile(configPath, JSON.stringify(config, null, 2))
|
||||
}
|
||||
|
||||
private async ensureDevLoggingInConfig(): Promise<void> {
|
||||
if (process.env.NODE_ENV !== 'development') return
|
||||
const configPath = join(this.openclawDir, OPENCLAW_CONFIG_FILE)
|
||||
if (!existsSync(configPath)) return
|
||||
try {
|
||||
const raw = await readFile(configPath, 'utf-8')
|
||||
const parsed = JSON.parse(raw) as Record<string, unknown>
|
||||
const existing = (parsed.logging ?? {}) as Record<string, unknown>
|
||||
if (existing.level === 'debug' && existing.consoleLevel === 'debug') {
|
||||
return
|
||||
}
|
||||
parsed.logging = { ...existing, level: 'debug', consoleLevel: 'debug' }
|
||||
await writeFile(configPath, JSON.stringify(parsed, null, 2))
|
||||
logger.info('Patched openclaw.json for dev debug logging')
|
||||
} catch (err) {
|
||||
logger.warn('Failed to patch openclaw.json for dev debug logging', {
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private startGatewayLogTail(): void {
|
||||
if (process.env.NODE_ENV !== 'development') return
|
||||
if (this.stopLogTail) return
|
||||
try {
|
||||
this.stopLogTail = this.runtime.tailGatewayLogs((line) => {
|
||||
logger.debug(`[openclaw] ${line}`)
|
||||
})
|
||||
logger.info('Streaming OpenClaw gateway logs into server log (dev mode)')
|
||||
} catch (err) {
|
||||
logger.warn('Failed to start OpenClaw gateway log tail', {
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private stopGatewayLogTail(): void {
|
||||
if (!this.stopLogTail) return
|
||||
try {
|
||||
this.stopLogTail()
|
||||
} catch {
|
||||
// best effort
|
||||
}
|
||||
this.stopLogTail = null
|
||||
}
|
||||
|
||||
private getHostWorkspaceDir(agentName: string): string {
|
||||
return join(
|
||||
this.openclawDir,
|
||||
|
||||
@@ -162,6 +162,32 @@ export class PodmanRuntime {
|
||||
return proc.exited
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow container logs. Returns a stop function that terminates the
|
||||
* underlying `podman logs -f` process. Each output line is passed to
|
||||
* onLine as-is.
|
||||
*/
|
||||
tailContainerLogs(containerName: string, onLine: LogFn): () => void {
|
||||
const proc = Bun.spawn(
|
||||
[this.podmanPath, 'logs', '-f', '--tail', '0', containerName],
|
||||
{ stdout: 'pipe', stderr: 'pipe' },
|
||||
)
|
||||
|
||||
void this.drainStream(proc.stdout ?? null, onLine)
|
||||
void this.drainStream(proc.stderr ?? null, onLine)
|
||||
|
||||
let stopped = false
|
||||
return () => {
|
||||
if (stopped) return
|
||||
stopped = true
|
||||
try {
|
||||
proc.kill()
|
||||
} catch {
|
||||
// process may already be gone
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists running container names. Used to check whether non-BrowserOS
|
||||
* containers are running before stopping the Podman machine.
|
||||
|
||||
Reference in New Issue
Block a user