Commit 0a6752f5 authored by AI-甘富林's avatar AI-甘富林

fix: 优化 bundled runtime 启动恢复与项目包同步状态

parent f8e4ee3a
...@@ -114,6 +114,7 @@ interface RendererSmokeState { ...@@ -114,6 +114,7 @@ interface RendererSmokeState {
const forcedUserDataPath = process.env.QJCLAW_USER_DATA_PATH?.trim(); const forcedUserDataPath = process.env.QJCLAW_USER_DATA_PATH?.trim();
const forcedLogsPath = process.env.QJCLAW_LOGS_PATH?.trim(); const forcedLogsPath = process.env.QJCLAW_LOGS_PATH?.trim();
const PROJECT_BUNDLE_BOOTSTRAP_TIMEOUT_MS = 45_000;
if (forcedUserDataPath) { if (forcedUserDataPath) {
app.setPath("userData", forcedUserDataPath); app.setPath("userData", forcedUserDataPath);
...@@ -129,6 +130,24 @@ function delay(ms: number): Promise<void> { ...@@ -129,6 +130,24 @@ function delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
} }
function withTimeout<T>(operation: Promise<T>, timeoutMs: number, message: string): Promise<T> {
return new Promise<T>((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(message));
}, timeoutMs);
operation.then(
(value) => {
clearTimeout(timer);
resolve(value);
},
(error) => {
clearTimeout(timer);
reject(error);
}
);
});
}
function buildSystemSummary(): SystemSummary { function buildSystemSummary(): SystemSummary {
const userDataPath = process.env.QJCLAW_USER_DATA_PATH?.trim() || app.getPath("userData"); const userDataPath = process.env.QJCLAW_USER_DATA_PATH?.trim() || app.getPath("userData");
const logsPath = process.env.QJCLAW_LOGS_PATH?.trim() || app.getPath("logs"); const logsPath = process.env.QJCLAW_LOGS_PATH?.trim() || app.getPath("logs");
...@@ -633,19 +652,37 @@ async function bootstrap(): Promise<void> { ...@@ -633,19 +652,37 @@ async function bootstrap(): Promise<void> {
configVersion: string | undefined, configVersion: string | undefined,
action: RuntimeCloudFetchAction action: RuntimeCloudFetchAction
) => { ) => {
console.info("[bundle-bootstrap]", "bundle.sync.start", {
action,
configVersion,
skillCount: skills.length,
timeoutMs: PROJECT_BUNDLE_BOOTSTRAP_TIMEOUT_MS
});
await traceBootstrap("project-bundle-sync:" + action + ":start:" + skills.length); await traceBootstrap("project-bundle-sync:" + action + ":start:" + skills.length);
try { try {
await projectBundleService.syncRemoteBundles(skills, configVersion, action); await withTimeout(
projectBundleService.syncRemoteBundles(skills, configVersion, action),
PROJECT_BUNDLE_BOOTSTRAP_TIMEOUT_MS,
`Project bundle sync timed out after ${Math.round(PROJECT_BUNDLE_BOOTSTRAP_TIMEOUT_MS / 1000)}s.`
);
console.info("[bundle-bootstrap]", "bundle.sync.done", {
action,
configVersion,
skillCount: skills.length
});
await traceBootstrap("project-bundle-sync:" + action + ":done"); await traceBootstrap("project-bundle-sync:" + action + ":done");
} catch (error) { } catch (error) {
const message = error instanceof Error ? (error.stack ?? error.message) : String(error); const rawMessage = error instanceof Error ? (error.stack ?? error.message) : String(error);
console.error("Project bundle sync failed", { const userMessage = error instanceof Error ? error.message : String(error);
projectBundleService.setSyncError(userMessage);
console.error("[bundle-bootstrap]", "bundle.sync.error", {
action, action,
configVersion, configVersion,
skillIds: skills.map((skill) => skill.skillId), skillIds: skills.map((skill) => skill.skillId),
error: message error: rawMessage,
timedOut: userMessage.includes("timed out")
}); });
await traceBootstrap("project-bundle-sync:" + action + ":error:" + message.replace(/\r?\n/g, " | ")); await traceBootstrap("project-bundle-sync:" + action + ":error:" + rawMessage.replace(/\r?\n/g, " | "));
} }
}; };
const projectContextService = new ProjectContextService(projectStore); const projectContextService = new ProjectContextService(projectStore);
...@@ -755,6 +792,7 @@ async function bootstrap(): Promise<void> { ...@@ -755,6 +792,7 @@ async function bootstrap(): Promise<void> {
dailyReportService, dailyReportService,
runtimeSkillBridge, runtimeSkillBridge,
projectStore, projectStore,
projectBundleService,
projectContextService, projectContextService,
projectChatTargetResolver, projectChatTargetResolver,
projectSkillRouter, projectSkillRouter,
......
...@@ -36,6 +36,7 @@ import type { SecretManager } from "./services/secrets.js"; ...@@ -36,6 +36,7 @@ import type { SecretManager } from "./services/secrets.js";
import type { RuntimeCloudSupervisor } from "./services/runtime-cloud-supervisor.js"; import type { RuntimeCloudSupervisor } from "./services/runtime-cloud-supervisor.js";
import type { RuntimeSkillBridgeService } from "./services/runtime-skill-bridge.js"; import type { RuntimeSkillBridgeService } from "./services/runtime-skill-bridge.js";
import type { ProjectStoreService } from "./services/project-store.js"; import type { ProjectStoreService } from "./services/project-store.js";
import type { ProjectBundleService } from "./services/project-bundle.js";
import { import {
EMPTY_PROJECT_INVENTORY_MESSAGE, EMPTY_PROJECT_INVENTORY_MESSAGE,
createSessionForActiveProject, createSessionForActiveProject,
...@@ -77,6 +78,7 @@ interface MainServices { ...@@ -77,6 +78,7 @@ interface MainServices {
dailyReportService: DailyReportService; dailyReportService: DailyReportService;
runtimeSkillBridge: RuntimeSkillBridgeService; runtimeSkillBridge: RuntimeSkillBridgeService;
projectStore: ProjectStoreService; projectStore: ProjectStoreService;
projectBundleService: ProjectBundleService;
projectContextService: ProjectContextService; projectContextService: ProjectContextService;
projectChatTargetResolver: ProjectChatTargetResolverService; projectChatTargetResolver: ProjectChatTargetResolverService;
projectSkillRouter: ProjectSkillRouterService; projectSkillRouter: ProjectSkillRouterService;
...@@ -215,6 +217,7 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -215,6 +217,7 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
dailyReportService, dailyReportService,
runtimeSkillBridge, runtimeSkillBridge,
projectStore, projectStore,
projectBundleService,
projectContextService, projectContextService,
projectChatTargetResolver, projectChatTargetResolver,
projectSkillRouter, projectSkillRouter,
...@@ -516,19 +519,29 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -516,19 +519,29 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
sessions, sessions,
skills skills
} = await loadActiveProjectWorkspaceState(projectStore); } = await loadActiveProjectWorkspaceState(projectStore);
const bundleSyncStatus = projectBundleService.getSyncStatus();
const bundleSyncFailed = bundleSyncStatus.state === "error";
const chatSummary = projects.length > 0 const chatSummary = projects.length > 0
? baseChatSummary ? baseChatSummary
: { : bundleSyncFailed
chatReady: false, ? {
chatLaunchState: config.apiKeyConfigured ? "starting" as const : baseChatSummary.chatLaunchState, chatReady: false,
chatStatusMessage: config.apiKeyConfigured chatLaunchState: "error" as const,
? EMPTY_PROJECT_INVENTORY_MESSAGE chatStatusMessage: bundleSyncStatus.lastError ?? "工作配置同步失败,请检查网络后重试。",
: baseChatSummary.chatStatusMessage, startupPhase: "error" as const,
startupPhase: config.apiKeyConfigured ? "syncing-config" as const : baseChatSummary.startupPhase, startupMessage: bundleSyncStatus.lastError ?? "工作配置同步失败,请检查网络后重试。"
startupMessage: config.apiKeyConfigured }
? EMPTY_PROJECT_INVENTORY_MESSAGE : {
: baseChatSummary.startupMessage chatReady: false,
}; chatLaunchState: config.apiKeyConfigured ? "starting" as const : baseChatSummary.chatLaunchState,
chatStatusMessage: config.apiKeyConfigured
? EMPTY_PROJECT_INVENTORY_MESSAGE
: baseChatSummary.chatStatusMessage,
startupPhase: config.apiKeyConfigured ? "syncing-config" as const : baseChatSummary.startupPhase,
startupMessage: config.apiKeyConfigured
? EMPTY_PROJECT_INVENTORY_MESSAGE
: baseChatSummary.startupMessage
};
return { return {
apiKeyConfigured: config.apiKeyConfigured, apiKeyConfigured: config.apiKeyConfigured,
......
...@@ -26,7 +26,7 @@ export function isTransientLocalGatewayError(message?: string): boolean { ...@@ -26,7 +26,7 @@ export function isTransientLocalGatewayError(message?: string): boolean {
|| normalized.includes("bundled runtime exited before gateway became ready"); || normalized.includes("bundled runtime exited before gateway became ready");
} }
function isGatewayPolicyViolationError(message?: string): boolean { export function isGatewayPolicyViolationError(message?: string): boolean {
if (!message) { if (!message) {
return false; return false;
} }
...@@ -37,7 +37,7 @@ function isGatewayPolicyViolationError(message?: string): boolean { ...@@ -37,7 +37,7 @@ function isGatewayPolicyViolationError(message?: string): boolean {
|| normalized.includes("policy violation"); || normalized.includes("policy violation");
} }
function isBundledRuntimeNameConflictError(message?: string): boolean { export function isBundledRuntimeNameConflictError(message?: string): boolean {
if (!message) { if (!message) {
return false; return false;
} }
...@@ -59,7 +59,7 @@ export function toStartupErrorMessage(message: string | undefined, fallback: str ...@@ -59,7 +59,7 @@ export function toStartupErrorMessage(message: string | undefined, fallback: str
} }
if (isGatewayPolicyViolationError(message)) { if (isGatewayPolicyViolationError(message)) {
return "检测到本机已有 OpenClaw 网关正在运行,但安装包未能切换到内置运行时,请先退出本地 OpenClaw 后重试。"; return "内置运行时网关连接被拒绝,如本机正在运行 OpenClaw,请先退出后重试;否则请重启应用。";
} }
return message ?? fallback; return message ?? fallback;
...@@ -75,7 +75,10 @@ export function shouldRetryManagedRuntimeStartup(config: AppConfig, status: Runt ...@@ -75,7 +75,10 @@ export function shouldRetryManagedRuntimeStartup(config: AppConfig, status: Runt
return false; return false;
} }
return isTransientLocalGatewayError(status.lastError ?? status.message); const runtimeError = status.lastError ?? status.message;
return isTransientLocalGatewayError(runtimeError)
|| isGatewayPolicyViolationError(runtimeError)
|| isBundledRuntimeNameConflictError(runtimeError);
} }
export function shouldRetryBootstrapWarmup(input: { export function shouldRetryBootstrapWarmup(input: {
...@@ -95,12 +98,20 @@ export function shouldRetryBootstrapWarmup(input: { ...@@ -95,12 +98,20 @@ export function shouldRetryBootstrapWarmup(input: {
} }
const runtimeError = input.runtimeStatus.lastError ?? input.runtimeStatus.message; const runtimeError = input.runtimeStatus.lastError ?? input.runtimeStatus.message;
if (input.runtimeStatus.processState === "error" && isTransientLocalGatewayError(runtimeError)) { if (input.runtimeStatus.processState === "error" && (
isTransientLocalGatewayError(runtimeError)
|| isGatewayPolicyViolationError(runtimeError)
|| isBundledRuntimeNameConflictError(runtimeError)
)) {
return true; return true;
} }
const gatewayError = input.gatewayStatus?.lastError ?? input.gatewayStatus?.message; const gatewayError = input.gatewayStatus?.lastError ?? input.gatewayStatus?.message;
return input.gatewayStatus?.state === "error" && isTransientLocalGatewayError(gatewayError); return input.gatewayStatus?.state === "error" && (
isTransientLocalGatewayError(gatewayError)
|| isGatewayPolicyViolationError(gatewayError)
|| isBundledRuntimeNameConflictError(gatewayError)
);
} }
export function buildChatSummary(input: { export function buildChatSummary(input: {
...@@ -166,7 +177,7 @@ export function buildChatSummary(input: { ...@@ -166,7 +177,7 @@ export function buildChatSummary(input: {
warmupInFlight warmupInFlight
&& packagedBundledRuntime && packagedBundledRuntime
&& gatewayStatus?.state === "error" && gatewayStatus?.state === "error"
&& isTransientLocalGatewayError(gatewayError) && (isTransientLocalGatewayError(gatewayError) || isGatewayPolicyViolationError(gatewayError) || isBundledRuntimeNameConflictError(gatewayError))
) { ) {
return { return {
chatReady: false, chatReady: false,
......
param( param(
[int]$GatewayPort = 18889, [int]$GatewayPort = 18889,
[string]$GatewayToken = 'qjc-bundled-runtime-token', [string]$GatewayToken = 'qjc-bundled-runtime-token',
[string]$SmokeOutput, [string]$SmokeOutput,
[string]$UserDataPath, [string]$UserDataPath,
[string]$LogsPath, [string]$LogsPath,
[int]$TimeoutSeconds = 90 [int]$TimeoutSeconds = 90,
[switch]$SkipMaterializeRuntime
) )
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
function Write-Utf8File {
param([string]$filePath, [string]$content)
$encoding = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($filePath, $content, $encoding)
}
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path $repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
if (-not $SmokeOutput) { if (-not $SmokeOutput) {
$SmokeOutput = Join-Path $repoRoot '.tmp\bundled-runtime-smoke\result.json' $SmokeOutput = Join-Path $repoRoot '.tmp\bundled-runtime-smoke\result.json'
...@@ -20,11 +27,77 @@ if (-not $LogsPath) { ...@@ -20,11 +27,77 @@ if (-not $LogsPath) {
$LogsPath = Join-Path $repoRoot '.tmp\bundled-runtime-smoke\logs' $LogsPath = Join-Path $repoRoot '.tmp\bundled-runtime-smoke\logs'
} }
Write-Host "Materializing bundled runtime payload on port $GatewayPort" $BaseOutputDir = [System.IO.Path]::GetFullPath((Split-Path $SmokeOutput -Parent))
powershell -ExecutionPolicy Bypass -File (Join-Path $repoRoot 'build\scripts\materialize-runtime-payload.ps1') -GatewayPort $GatewayPort -GatewayToken $GatewayToken $userDataPath = [System.IO.Path]::GetFullPath($UserDataPath)
if ($LASTEXITCODE -ne 0) { $logsPath = [System.IO.Path]::GetFullPath($LogsPath)
exit $LASTEXITCODE $bundleSourceRoot = Join-Path $BaseOutputDir 'bundle-src'
$bundleRoot = Join-Path $bundleSourceRoot 'bundled-runtime-smoke'
$bundleZipPath = Join-Path $BaseOutputDir 'bundled-runtime-smoke.zip'
$bundleFileName = 'bundled-runtime-smoke.zip'
$bundleProjectId = 'bundled-runtime-smoke'
$bundleProjectName = ''
$bundleSkillId = 'bundled-runtime-smoke-skill'
$bundleConfigVersion = '2026-04-03T03:55:00.000Z'
$bundleReadmeMarker = 'Bundled runtime smoke bundle marker.'
if (Test-Path $BaseOutputDir) {
Remove-Item $BaseOutputDir -Recurse -Force -ErrorAction SilentlyContinue
}
New-Item -ItemType Directory -Force -Path $BaseOutputDir, $bundleRoot, $userDataPath, $logsPath | Out-Null
$bundleProjectJson = [ordered]@{
id = $bundleProjectId
name = $bundleProjectName
description = 'Remote bundle fixture for bundled runtime smoke.'
version = '1.0.0'
}
$bundleReadme = @(
'# Bundled Runtime Smoke',
'',
'This bundle validates that bundled runtime startup can sync a zip-backed project bundle.',
$bundleReadmeMarker
) -join [Environment]::NewLine
$bundleAgent = @(
'# Bundled Runtime Smoke',
'',
'This project is used to validate remote zip bundle sync during packaged desktop startup.'
) -join [Environment]::NewLine
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
Write-Utf8File (Join-Path $bundleRoot 'project.json') ($bundleProjectJson | ConvertTo-Json -Depth 5)
Write-Utf8File (Join-Path $bundleRoot 'README.md') $bundleReadme
Write-Utf8File (Join-Path $bundleRoot 'AGENT.md') $bundleAgent
New-Item -ItemType Directory -Force -Path (Join-Path $bundleRoot 'memory') | Out-Null
Write-Utf8File (Join-Path $bundleRoot 'memory\summary.md') 'Bundled runtime smoke memory marker.'
if (Test-Path $bundleZipPath) {
Remove-Item $bundleZipPath -Force -ErrorAction SilentlyContinue
}
Compress-Archive -Path (Join-Path $bundleSourceRoot '*') -DestinationPath $bundleZipPath -Force
if (-not $SkipMaterializeRuntime) {
Write-Host "Materializing bundled runtime payload on port $GatewayPort"
powershell -ExecutionPolicy Bypass -File (Join-Path $repoRoot 'build\scripts\materialize-runtime-payload.ps1') -GatewayPort $GatewayPort -GatewayToken $GatewayToken
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
} }
powershell -ExecutionPolicy Bypass -File (Join-Path $repoRoot 'build\scripts\electron-smoke.ps1') -SmokeOutput $SmokeOutput -UserDataPath $UserDataPath -LogsPath $LogsPath -RuntimeMode 'bundled-runtime' -ExpectBundledRuntime -TimeoutSeconds $TimeoutSeconds $env:QJCLAW_SMOKE_BUNDLE_ZIP_PATH = $bundleZipPath
exit $LASTEXITCODE $env:QJCLAW_SMOKE_BUNDLE_FILE_NAME = $bundleFileName
$env:QJCLAW_SMOKE_BUNDLE_SKILL_ID = $bundleSkillId
$env:QJCLAW_SMOKE_BUNDLE_SKILL_TITLE = 'Bundled Runtime Smoke Skill'
$env:QJCLAW_SMOKE_BUNDLE_SKILL_DESCRIPTION = 'Remote zip-backed bundle for bundled runtime smoke validation.'
$env:QJCLAW_SMOKE_BUNDLE_CONFIG_VERSION = $bundleConfigVersion
try {
powershell -ExecutionPolicy Bypass -File (Join-Path $repoRoot 'build\scripts\electron-smoke.ps1') -SmokeOutput $SmokeOutput -UserDataPath $UserDataPath -LogsPath $LogsPath -RuntimeMode 'bundled-runtime' -ExpectBundledRuntime -ExpectRemoteBundle -WorkspaceProjectId $bundleProjectId -SmokePrompt 'Describe the current project root and confirm bundled runtime smoke bundle execution.' -SmokeSkillId '__bundled_runtime_smoke_disabled__' -ExpectedBundleSourceUrl "http://127.0.0.1:$GatewayPort/downloads/$bundleFileName" -ExpectedBundleConfigVersion $bundleConfigVersion -ExpectedBundleFileName $bundleFileName -ExpectedBundleSkillId $bundleSkillId -ExpectedReadmeMarker $bundleReadmeMarker -TimeoutSeconds $TimeoutSeconds
exit $LASTEXITCODE
} finally {
Remove-Item Env:QJCLAW_SMOKE_BUNDLE_ZIP_PATH -ErrorAction SilentlyContinue
Remove-Item Env:QJCLAW_SMOKE_BUNDLE_FILE_NAME -ErrorAction SilentlyContinue
Remove-Item Env:QJCLAW_SMOKE_BUNDLE_SKILL_ID -ErrorAction SilentlyContinue
Remove-Item Env:QJCLAW_SMOKE_BUNDLE_SKILL_TITLE -ErrorAction SilentlyContinue
Remove-Item Env:QJCLAW_SMOKE_BUNDLE_SKILL_DESCRIPTION -ErrorAction SilentlyContinue
Remove-Item Env:QJCLAW_SMOKE_BUNDLE_CONFIG_VERSION -ErrorAction SilentlyContinue
}
...@@ -361,7 +361,7 @@ if (expectRemoteBundle === 'true') { ...@@ -361,7 +361,7 @@ if (expectRemoteBundle === 'true') {
if (currentProjectId !== workspaceProjectId) { if (currentProjectId !== workspaceProjectId) {
throw new Error('Remote bundle smoke did not activate the expected project: ' + currentProjectId); throw new Error('Remote bundle smoke did not activate the expected project: ' + currentProjectId);
} }
if (currentProjectName !== workspaceProjectName) { if (workspaceProjectName && currentProjectName !== workspaceProjectName) {
throw new Error('Remote bundle smoke did not expose the expected project name: ' + currentProjectName); throw new Error('Remote bundle smoke did not expose the expected project name: ' + currentProjectName);
} }
if (!fs.existsSync(bundleManifestPath)) { if (!fs.existsSync(bundleManifestPath)) {
......
...@@ -9,8 +9,12 @@ import { ...@@ -9,8 +9,12 @@ import {
} from "../../apps/desktop/src/main/services/openclaw-local-config.js"; } from "../../apps/desktop/src/main/services/openclaw-local-config.js";
import { import {
buildChatSummary, buildChatSummary,
isBundledRuntimeNameConflictError,
isGatewayPolicyViolationError,
isTransientLocalGatewayError, isTransientLocalGatewayError,
shouldRetryBootstrapWarmup shouldRetryBootstrapWarmup,
shouldRetryManagedRuntimeStartup,
toStartupErrorMessage
} from "../../apps/desktop/src/main/workspace-startup.js"; } from "../../apps/desktop/src/main/workspace-startup.js";
function assert(condition: unknown, message: string): asserts condition { function assert(condition: unknown, message: string): asserts condition {
...@@ -122,6 +126,10 @@ async function main(): Promise<void> { ...@@ -122,6 +126,10 @@ async function main(): Promise<void> {
for (const message of transientCodes) { for (const message of transientCodes) {
assert(isTransientLocalGatewayError(message), `Expected transient startup classification for: ${message}`); assert(isTransientLocalGatewayError(message), `Expected transient startup classification for: ${message}`);
} }
assert(isGatewayPolicyViolationError("Gateway connection closed (1008)."), "Expected 1008 to be classified as gateway policy violation.");
assert(isBundledRuntimeNameConflictError("gateway name/hostname conflict detected via bonjour"), "Expected bonjour name conflict to be classified correctly.");
const policyViolationMessage = toStartupErrorMessage("Gateway connection closed (1008).", "fallback");
assert(!policyViolationMessage.includes("本机已有 OpenClaw 网关正在运行"), "Policy violation message should no longer hard-assert a local OpenClaw gateway conflict.");
const config = createConfig(); const config = createConfig();
const runtimeStatus = createRuntimeStatus({ const runtimeStatus = createRuntimeStatus({
...@@ -162,6 +170,36 @@ async function main(): Promise<void> { ...@@ -162,6 +170,36 @@ async function main(): Promise<void> {
gatewayStatus, gatewayStatus,
isPackaged: true isPackaged: true
}), "Packaged bundled-runtime bootstrap should retry transient startup failures."); }), "Packaged bundled-runtime bootstrap should retry transient startup failures.");
assert(shouldRetryManagedRuntimeStartup(config, runtimeStatus), "Packaged bundled-runtime startup should retry transient runtime failures.");
const policyViolationRuntimeStatus = createRuntimeStatus({
processState: "error",
lastError: "Gateway connection closed (1008)."
});
const policyViolationGatewayStatus = createGatewayStatus({
state: "error",
lastError: "Gateway connection closed (1008).",
message: "Gateway connection closed (1008)."
});
assert(shouldRetryBootstrapWarmup({
config,
runtimeStatus: createRuntimeStatus(),
gatewayStatus: policyViolationGatewayStatus,
isPackaged: true
}), "Packaged bundled-runtime bootstrap should retry gateway policy violations.");
assert(shouldRetryManagedRuntimeStartup(config, policyViolationRuntimeStatus), "Packaged bundled-runtime startup should retry runtime policy violations.");
const nameConflictGatewayStatus = createGatewayStatus({
state: "error",
lastError: "Gateway name/hostname conflict detected via bonjour.",
message: "Gateway name/hostname conflict detected via bonjour."
});
assert(shouldRetryBootstrapWarmup({
config,
runtimeStatus: createRuntimeStatus(),
gatewayStatus: nameConflictGatewayStatus,
isPackaged: true
}), "Packaged bundled-runtime bootstrap should retry bundled runtime name conflicts.");
assert(!shouldUseLocalOpenClawGateway(true, "bundled-runtime"), "Packaged bundled-runtime mode should ignore local OpenClaw."); assert(!shouldUseLocalOpenClawGateway(true, "bundled-runtime"), "Packaged bundled-runtime mode should ignore local OpenClaw.");
assert(shouldUseLocalOpenClawGateway(true, "external-gateway"), "Packaged external-gateway mode should allow local OpenClaw."); assert(shouldUseLocalOpenClawGateway(true, "external-gateway"), "Packaged external-gateway mode should allow local OpenClaw.");
...@@ -180,6 +218,8 @@ async function main(): Promise<void> { ...@@ -180,6 +218,8 @@ async function main(): Promise<void> {
startupSummary, startupSummary,
gatewayOnlySummary, gatewayOnlySummary,
shouldRetryBootstrap: true, shouldRetryBootstrap: true,
shouldRetryManagedRuntime: true,
policyViolationMessage,
localOpenClawPolicy: { localOpenClawPolicy: {
packagedBundledRuntime: shouldUseLocalOpenClawGateway(true, "bundled-runtime"), packagedBundledRuntime: shouldUseLocalOpenClawGateway(true, "bundled-runtime"),
packagedExternalGateway: shouldUseLocalOpenClawGateway(true, "external-gateway"), packagedExternalGateway: shouldUseLocalOpenClawGateway(true, "external-gateway"),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment