Commit c8f666ff authored by edy's avatar edy

Refine packaged startup diagnostics

parent ccc9192e
...@@ -22,14 +22,17 @@ export function isTransientLocalGatewayError(message?: string): boolean { ...@@ -22,14 +22,17 @@ export function isTransientLocalGatewayError(message?: string): boolean {
|| normalized.includes("econnrefused") || normalized.includes("econnrefused")
|| normalized.includes("failed to connect to ws://127.0.0.1") || normalized.includes("failed to connect to ws://127.0.0.1")
|| normalized.includes("failed to connect to ws://localhost") || normalized.includes("failed to connect to ws://localhost")
|| normalized.includes("gateway readiness") || normalized.includes("timed out while connecting to ws://127.0.0.1")
|| normalized.includes("gateway became ready") || normalized.includes("timed out while connecting to ws://localhost")
|| normalized.includes("gateway closed during readiness probe") || normalized.includes("gateway closed during readiness probe")
|| normalized.includes("gateway closed before readiness probe completed") || normalized.includes("gateway closed before readiness probe completed")
|| normalized.includes("gateway closed before health probe completed") || normalized.includes("gateway closed before health probe completed")
|| normalized.includes("timed out while probing reusable gateway") || normalized.includes("timed out while probing reusable gateway")
|| normalized.includes("timed out while waiting for reusable gateway") || normalized.includes("timed out while probing bundled gateway")
|| normalized.includes("bundled runtime exited before gateway became ready"); || normalized.includes("gateway health probe reported not ok")
|| normalized.includes("gateway starting; retry shortly")
|| normalized.includes("startup-sidecars")
|| normalized.includes("unavailable");
} }
export function isGatewayPolicyViolationError(message?: string): boolean { export function isGatewayPolicyViolationError(message?: string): boolean {
...@@ -57,7 +60,11 @@ export function isBundledRuntimeNameConflictError(message?: string): boolean { ...@@ -57,7 +60,11 @@ export function isBundledRuntimeNameConflictError(message?: string): boolean {
export function toStartupErrorMessage(message: string | undefined, fallback: string): string { export function toStartupErrorMessage(message: string | undefined, fallback: string): string {
if (isTransientLocalGatewayError(message)) { if (isTransientLocalGatewayError(message)) {
return "本地助手暂时没有准备好,请重试。"; return "本地助手正在启动,请稍候。";
}
if (message?.toLowerCase().includes("macsecurityblock")) {
return "macOS 阻止了内置运行时启动。请在系统安全设置中允许应用运行,或移除应用隔离属性后重试。";
} }
if (isBundledRuntimeNameConflictError(message)) { if (isBundledRuntimeNameConflictError(message)) {
...@@ -82,9 +89,7 @@ export function shouldRetryManagedRuntimeStartup(config: AppConfig, status: Runt ...@@ -82,9 +89,7 @@ export function shouldRetryManagedRuntimeStartup(config: AppConfig, status: Runt
} }
const runtimeError = status.lastError ?? status.message; const runtimeError = status.lastError ?? status.message;
return isTransientLocalGatewayError(runtimeError) return isTransientLocalGatewayError(runtimeError);
|| isGatewayPolicyViolationError(runtimeError)
|| isBundledRuntimeNameConflictError(runtimeError);
} }
export function shouldRetryBootstrapWarmup(input: { export function shouldRetryBootstrapWarmup(input: {
...@@ -104,20 +109,12 @@ export function shouldRetryBootstrapWarmup(input: { ...@@ -104,20 +109,12 @@ 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" && ( if (input.runtimeStatus.processState === "error" && isTransientLocalGatewayError(runtimeError)) {
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" && ( return input.gatewayStatus?.state === "error" && isTransientLocalGatewayError(gatewayError);
isTransientLocalGatewayError(gatewayError)
|| isGatewayPolicyViolationError(gatewayError)
|| isBundledRuntimeNameConflictError(gatewayError)
);
} }
export function requiresShellWarmupBeforeBinding(config: AppConfig): boolean { export function requiresShellWarmupBeforeBinding(config: AppConfig): boolean {
...@@ -169,7 +166,9 @@ function buildSetupSummary(config: AppConfig): Pick<WorkspaceSummary, "chatReady ...@@ -169,7 +166,9 @@ function buildSetupSummary(config: AppConfig): Pick<WorkspaceSummary, "chatReady
} }
function buildRuntimeStartingSummary(runtimeStatus: RuntimeStatus): Pick<WorkspaceSummary, "chatReady" | "chatLaunchState" | "chatStatusMessage" | "startupPhase" | "startupMessage"> { function buildRuntimeStartingSummary(runtimeStatus: RuntimeStatus): Pick<WorkspaceSummary, "chatReady" | "chatLaunchState" | "chatStatusMessage" | "startupPhase" | "startupMessage"> {
const message = runtimeStatus.message || "正在唤起本地助手,请稍候。"; const message = runtimeStatus.selectedMode === "bundled-runtime"
? "本地助手正在启动,请稍候。"
: runtimeStatus.message || "正在唤起本地助手,请稍候。";
return { return {
chatReady: false, chatReady: false,
chatLaunchState: "starting", chatLaunchState: "starting",
...@@ -180,7 +179,10 @@ function buildRuntimeStartingSummary(runtimeStatus: RuntimeStatus): Pick<Workspa ...@@ -180,7 +179,10 @@ function buildRuntimeStartingSummary(runtimeStatus: RuntimeStatus): Pick<Workspa
} }
function buildGatewayStartingSummary(gatewayStatus: GatewayStatus | null): Pick<WorkspaceSummary, "chatReady" | "chatLaunchState" | "chatStatusMessage" | "startupPhase" | "startupMessage"> { function buildGatewayStartingSummary(gatewayStatus: GatewayStatus | null): Pick<WorkspaceSummary, "chatReady" | "chatLaunchState" | "chatStatusMessage" | "startupPhase" | "startupMessage"> {
const message = gatewayStatus?.message ?? "正在连接聊天服务,请稍候。"; const gatewayMessage = gatewayStatus?.lastError ?? gatewayStatus?.message;
const message = isTransientLocalGatewayError(gatewayMessage)
? "本地助手正在启动,请稍候。"
: gatewayStatus?.message ?? "正在连接聊天服务,请稍候。";
return { return {
chatReady: false, chatReady: false,
chatLaunchState: "starting", chatLaunchState: "starting",
...@@ -268,9 +270,9 @@ export function buildChatSummary(input: { ...@@ -268,9 +270,9 @@ export function buildChatSummary(input: {
return { return {
chatReady: false, chatReady: false,
chatLaunchState: "starting", chatLaunchState: "starting",
chatStatusMessage: "正在重新唤起本地助手,请稍候。", chatStatusMessage: "本地助手正在启动,请稍候。",
startupPhase: "starting-runtime", startupPhase: "starting-runtime",
startupMessage: "正在重新唤起本地助手,请稍候。" startupMessage: "本地助手正在启动,请稍候。"
}; };
} }
...@@ -278,14 +280,14 @@ export function buildChatSummary(input: { ...@@ -278,14 +280,14 @@ export function buildChatSummary(input: {
warmupInFlight warmupInFlight
&& packagedBundledRuntime && packagedBundledRuntime
&& gatewayStatus?.state === "error" && gatewayStatus?.state === "error"
&& (isTransientLocalGatewayError(gatewayError) || isGatewayPolicyViolationError(gatewayError) || isBundledRuntimeNameConflictError(gatewayError)) && isTransientLocalGatewayError(gatewayError)
) { ) {
return { return {
chatReady: false, chatReady: false,
chatLaunchState: "starting", chatLaunchState: "starting",
chatStatusMessage: "正在重新连接聊天服务,请稍候。", chatStatusMessage: "本地助手正在启动,请稍候。",
startupPhase: "connecting-gateway", startupPhase: "connecting-gateway",
startupMessage: "正在重新连接聊天服务,请稍候。" startupMessage: "本地助手正在启动,请稍候。"
}; };
} }
...@@ -345,9 +347,9 @@ export function buildChatSummary(input: { ...@@ -345,9 +347,9 @@ export function buildChatSummary(input: {
return { return {
chatReady: false, chatReady: false,
chatLaunchState: "starting", chatLaunchState: "starting",
chatStatusMessage: "正在重新唤起本地助手,请稍候。", chatStatusMessage: "本地助手正在启动,请稍候。",
startupPhase: "starting-runtime", startupPhase: "starting-runtime",
startupMessage: "正在重新唤起本地助手,请稍候。" startupMessage: "本地助手正在启动,请稍候。"
}; };
} }
...@@ -355,14 +357,14 @@ export function buildChatSummary(input: { ...@@ -355,14 +357,14 @@ export function buildChatSummary(input: {
warmupInFlight warmupInFlight
&& packagedBundledRuntime && packagedBundledRuntime
&& gatewayStatus?.state === "error" && gatewayStatus?.state === "error"
&& (isTransientLocalGatewayError(gatewayError) || isGatewayPolicyViolationError(gatewayError) || isBundledRuntimeNameConflictError(gatewayError)) && isTransientLocalGatewayError(gatewayError)
) { ) {
return { return {
chatReady: false, chatReady: false,
chatLaunchState: "starting", chatLaunchState: "starting",
chatStatusMessage: "正在重新连接聊天服务,请稍候。", chatStatusMessage: "本地助手正在启动,请稍候。",
startupPhase: "connecting-gateway", startupPhase: "connecting-gateway",
startupMessage: "正在重新连接聊天服务,请稍候。" startupMessage: "本地助手正在启动,请稍候。"
}; };
} }
......
...@@ -176,7 +176,10 @@ async function main(): Promise<void> { ...@@ -176,7 +176,10 @@ async function main(): Promise<void> {
"Gateway closed during connect (1000).", "Gateway closed during connect (1000).",
"Gateway closed before health probe completed (1000).", "Gateway closed before health probe completed (1000).",
"Timed out while probing reusable Gateway status and health at ws://127.0.0.1:18889.", "Timed out while probing reusable Gateway status and health at ws://127.0.0.1:18889.",
"Timed out while waiting for reusable Gateway at ws://127.0.0.1:18889.", "Timed out while probing bundled Gateway status and health at ws://127.0.0.1:18889.",
"Timed out while connecting to ws://127.0.0.1:18889.",
"Gateway health probe reported not ok.",
"gateway starting; retry shortly | startup-sidecars | UNAVAILABLE",
"connect ECONNREFUSED 127.0.0.1:18889" "connect ECONNREFUSED 127.0.0.1:18889"
]; ];
for (const message of transientCodes) { for (const message of transientCodes) {
...@@ -186,6 +189,8 @@ async function main(): Promise<void> { ...@@ -186,6 +189,8 @@ async function main(): Promise<void> {
assert(isBundledRuntimeNameConflictError("gateway name/hostname conflict detected via bonjour"), "Expected bonjour name conflict to be classified correctly."); 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"); 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."); assert(!policyViolationMessage.includes("本机已有 OpenClaw 网关正在运行"), "Policy violation message should no longer hard-assert a local OpenClaw gateway conflict.");
const macSecurityMessage = toStartupErrorMessage("macSecurityBlock: macOS blocked bundled runtime execution (Operation not permitted).", "fallback");
assert(macSecurityMessage.includes("macOS 阻止了内置运行时启动"), "mac security blocks should get a specific startup diagnostic.");
const config = createConfig(); const config = createConfig();
const runtimeStatus = createRuntimeStatus({ const runtimeStatus = createRuntimeStatus({
...@@ -210,6 +215,7 @@ async function main(): Promise<void> { ...@@ -210,6 +215,7 @@ async function main(): Promise<void> {
}); });
assert(startupSummary.chatLaunchState === "starting", "Transient packaged startup failures should remain in starting state."); assert(startupSummary.chatLaunchState === "starting", "Transient packaged startup failures should remain in starting state.");
assert(startupSummary.startupPhase === "starting-runtime", "Transient runtime failures should map to starting-runtime."); assert(startupSummary.startupPhase === "starting-runtime", "Transient runtime failures should map to starting-runtime.");
assert(startupSummary.startupMessage === "本地助手正在启动,请稍候。", "Transient runtime startup should use the unified local assistant starting message.");
const gatewayOnlySummary = buildChatSummary({ const gatewayOnlySummary = buildChatSummary({
config, config,
...@@ -223,6 +229,7 @@ async function main(): Promise<void> { ...@@ -223,6 +229,7 @@ async function main(): Promise<void> {
}); });
assert(gatewayOnlySummary.chatLaunchState === "starting", "Transient gateway failures should remain in starting state during packaged warmup."); assert(gatewayOnlySummary.chatLaunchState === "starting", "Transient gateway failures should remain in starting state during packaged warmup.");
assert(gatewayOnlySummary.startupPhase === "connecting-gateway", "Transient gateway failures should map to connecting-gateway."); assert(gatewayOnlySummary.startupPhase === "connecting-gateway", "Transient gateway failures should map to connecting-gateway.");
assert(gatewayOnlySummary.startupMessage === "本地助手正在启动,请稍候。", "Transient gateway startup should use the unified local assistant starting message.");
assert(shouldRetryBootstrapWarmup({ assert(shouldRetryBootstrapWarmup({
config, config,
...@@ -264,8 +271,8 @@ async function main(): Promise<void> { ...@@ -264,8 +271,8 @@ async function main(): Promise<void> {
runtimeStatus: createRuntimeStatus(), runtimeStatus: createRuntimeStatus(),
gatewayStatus: policyViolationGatewayStatus, gatewayStatus: policyViolationGatewayStatus,
isPackaged: true isPackaged: true
}), "Packaged bundled-runtime bootstrap should retry gateway policy violations."); }) === false, "Packaged bundled-runtime bootstrap should not retry gateway policy violations.");
assert(shouldRetryManagedRuntimeStartup(config, policyViolationRuntimeStatus), "Packaged bundled-runtime startup should retry runtime policy violations."); assert(!shouldRetryManagedRuntimeStartup(config, policyViolationRuntimeStatus), "Packaged bundled-runtime startup should not retry runtime policy violations.");
const nameConflictGatewayStatus = createGatewayStatus({ const nameConflictGatewayStatus = createGatewayStatus({
state: "error", state: "error",
...@@ -277,7 +284,7 @@ async function main(): Promise<void> { ...@@ -277,7 +284,7 @@ async function main(): Promise<void> {
runtimeStatus: createRuntimeStatus(), runtimeStatus: createRuntimeStatus(),
gatewayStatus: nameConflictGatewayStatus, gatewayStatus: nameConflictGatewayStatus,
isPackaged: true isPackaged: true
}), "Packaged bundled-runtime bootstrap should retry bundled runtime name conflicts."); }) === false, "Packaged bundled-runtime bootstrap should not retry bundled runtime name conflicts.");
const unboundConfig = createConfig({ const unboundConfig = createConfig({
apiKeyConfigured: false apiKeyConfigured: false
......
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