




















@@ -29,6 +29,12 @@ export type GatewayRestartSnapshot = {
2929portUsage: PortUsage;
3030healthy: boolean;
3131staleGatewayPids: number[];
32+gatewayVersion?: string | null;
33+expectedVersion?: string;
34+versionMismatch?: {
35+expected: string;
36+actual: string | null;
37+};
3238waitOutcome?: GatewayRestartWaitOutcome;
3339elapsedMs?: number;
3440};
@@ -38,6 +44,11 @@ export type GatewayPortHealthSnapshot = {
3844healthy: boolean;
3945};
404647+type GatewayReachability = {
48+reachable: boolean;
49+gatewayVersion: string | null;
50+};
51+4152function hasListenerAttributionGap(portUsage: PortUsage): boolean {
4253if (portUsage.status !== "busy" || portUsage.listeners.length > 0) {
4354return false;
@@ -69,7 +80,28 @@ function looksLikeAuthClose(code: number | undefined, reason: string | undefined
6980);
7081}
718272-async function confirmGatewayReachable(port: number): Promise<boolean> {
83+function applyExpectedVersion(
84+snapshot: GatewayRestartSnapshot,
85+expectedVersion: string | undefined,
86+): GatewayRestartSnapshot {
87+if (!expectedVersion) {
88+return snapshot;
89+}
90+if (snapshot.gatewayVersion === expectedVersion) {
91+return { ...snapshot, expectedVersion };
92+}
93+return {
94+ ...snapshot,
95+healthy: false,
96+ expectedVersion,
97+versionMismatch: {
98+expected: expectedVersion,
99+actual: snapshot.gatewayVersion ?? null,
100+},
101+};
102+}
103+104+async function confirmGatewayReachable(port: number): Promise<GatewayReachability> {
73105const token = normalizeOptionalString(process.env.OPENCLAW_GATEWAY_TOKEN);
74106const password = normalizeOptionalString(process.env.OPENCLAW_GATEWAY_PASSWORD);
75107const probe = await probeGateway({
@@ -78,7 +110,10 @@ async function confirmGatewayReachable(port: number): Promise<boolean> {
78110timeoutMs: 3_000,
79111includeDetails: false,
80112});
81-return probe.ok || looksLikeAuthClose(probe.close?.code, probe.close?.reason);
113+return {
114+reachable: probe.ok || looksLikeAuthClose(probe.close?.code, probe.close?.reason),
115+gatewayVersion: probe.server?.version ?? null,
116+};
82117}
8311884119async function inspectGatewayPortHealth(port: number): Promise<GatewayPortHealthSnapshot> {
@@ -98,7 +133,7 @@ async function inspectGatewayPortHealth(port: number): Promise<GatewayPortHealth
98133let healthy = false;
99134if (portUsage.status === "busy") {
100135try {
101-healthy = await confirmGatewayReachable(port);
136+healthy = (await confirmGatewayReachable(port)).reachable;
102137} catch {
103138// best-effort probe
104139}
@@ -111,9 +146,16 @@ export async function inspectGatewayRestart(params: {
111146service: GatewayService;
112147port: number;
113148env?: NodeJS.ProcessEnv;
149+expectedVersion?: string | null;
114150includeUnknownListenersAsStale?: boolean;
115151}): Promise<GatewayRestartSnapshot> {
116152const env = params.env ?? process.env;
153+const expectedVersion = normalizeOptionalString(params.expectedVersion);
154+let reachability: GatewayReachability | null = null;
155+const loadReachability = async () => {
156+reachability ??= await confirmGatewayReachable(params.port);
157+return reachability;
158+};
117159let runtime: GatewayServiceRuntime = { status: "unknown" };
118160try {
119161runtime = await params.service.readRuntime(env);
@@ -136,14 +178,18 @@ export async function inspectGatewayRestart(params: {
136178137179if (portUsage.status === "busy" && runtime.status !== "running") {
138180try {
139-const reachable = await confirmGatewayReachable(params.port);
140-if (reachable) {
141-return {
142- runtime,
143- portUsage,
144-healthy: true,
145-staleGatewayPids: [],
146-};
181+const reachable = await loadReachability();
182+if (reachable.reachable) {
183+return applyExpectedVersion(
184+{
185+ runtime,
186+ portUsage,
187+healthy: true,
188+staleGatewayPids: [],
189+gatewayVersion: reachable.gatewayVersion,
190+},
191+expectedVersion,
192+);
147193}
148194} catch {
149195// Probe is best-effort; keep the ownership-based diagnostics.
@@ -176,9 +222,21 @@ export async function inspectGatewayRestart(params: {
176222) || listenerAttributionGap
177223 : gatewayListeners.length > 0 || listenerAttributionGap;
178224let healthy = running && ownsPort;
179-if (!healthy && running && portUsage.status === "busy") {
225+let gatewayVersion: string | null | undefined;
226+if (expectedVersion && healthy && portUsage.status === "busy") {
180227try {
181-healthy = await confirmGatewayReachable(params.port);
228+const reachable = await loadReachability();
229+healthy = reachable.reachable;
230+gatewayVersion = reachable.gatewayVersion;
231+} catch {
232+healthy = false;
233+}
234+}
235+if (!healthy && running && portUsage.status === "busy" && !expectedVersion) {
236+try {
237+const reachable = await loadReachability();
238+healthy = reachable.reachable;
239+gatewayVersion = reachable.gatewayVersion;
182240} catch {
183241// best-effort probe
184242}
@@ -203,12 +261,16 @@ export async function inspectGatewayRestart(params: {
203261]),
204262);
205263206-return {
207- runtime,
208- portUsage,
209- healthy,
210- staleGatewayPids,
211-};
264+return applyExpectedVersion(
265+{
266+ runtime,
267+ portUsage,
268+ healthy,
269+ staleGatewayPids,
270+ ...(gatewayVersion !== undefined ? { gatewayVersion } : {}),
271+},
272+expectedVersion,
273+);
212274}
213275214276function shouldEarlyExitStoppedFree(
@@ -243,6 +305,7 @@ export async function waitForGatewayHealthyRestart(params: {
243305attempts?: number;
244306delayMs?: number;
245307env?: NodeJS.ProcessEnv;
308+expectedVersion?: string | null;
246309includeUnknownListenersAsStale?: boolean;
247310}): Promise<GatewayRestartSnapshot> {
248311const attempts = params.attempts ?? DEFAULT_RESTART_HEALTH_ATTEMPTS;
@@ -252,6 +315,7 @@ export async function waitForGatewayHealthyRestart(params: {
252315service: params.service,
253316port: params.port,
254317env: params.env,
318+expectedVersion: params.expectedVersion,
255319includeUnknownListenersAsStale: params.includeUnknownListenersAsStale,
256320});
257321@@ -282,6 +346,7 @@ export async function waitForGatewayHealthyRestart(params: {
282346service: params.service,
283347port: params.port,
284348env: params.env,
349+expectedVersion: params.expectedVersion,
285350includeUnknownListenersAsStale: params.includeUnknownListenersAsStale,
286351});
287352}
@@ -328,6 +393,12 @@ function renderPortUsageDiagnostics(snapshot: GatewayPortHealthSnapshot): string
328393329394export function renderRestartDiagnostics(snapshot: GatewayRestartSnapshot): string[] {
330395const lines: string[] = [];
396+if (snapshot.versionMismatch) {
397+const actual = snapshot.versionMismatch.actual ?? "unavailable";
398+lines.push(
399+`Gateway version mismatch: expected ${snapshot.versionMismatch.expected}, running gateway reported ${actual}.`,
400+);
401+}
331402const runtimeSummary = [
332403snapshot.runtime.status ? `status=${snapshot.runtime.status}` : null,
333404snapshot.runtime.state ? `state=${snapshot.runtime.state}` : null,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。