fix: surface upstream gateway timeout for remote OpenClaw/Tailscale connections (#94)

* surface gateway timeout for tailscale

* talescale fix #2 - attempt 1

* luke findings fix#1

* add narrow log for clientId

* prod safe proxy log

* fix log visibility

* LAN connection & subagent SOUL|IDENTITY fixes

* Initialize missing files for subagent SOUL|IDENTITY

* surface missing files in UI

* capturing agent - runtime,identity,session

* plugin-install fix

* fix: recover agent workspace for marketplace installs

* fix: recover agent workspace and identity name from file provenance

* fix: tolerate webchat session patch blocks during permission updates
This commit is contained in:
gsknnft
2026-04-03 18:57:36 -04:00
committed by GitHub
parent 4be98d7080
commit a18c8c630c
28 changed files with 1174 additions and 76 deletions
+39 -1
View File
@@ -20,6 +20,7 @@ import type {
} from "@/lib/studio/coordinator";
import { resolveStudioProxyGatewayUrl } from "@/lib/gateway/proxy-url";
import { ensureGatewayReloadModeHotForLocalStudio } from "@/lib/gateway/gatewayReloadMode";
import { isLocalGatewayUrl } from "@/lib/gateway/local-gateway";
import { GatewayResponseError } from "@/lib/gateway/errors";
const gatewayDebugEnabled = process.env.NODE_ENV !== "production";
@@ -117,10 +118,24 @@ const DEFAULT_UPSTREAM_GATEWAY_URL =
const DEFAULT_CUSTOM_RUNTIME_URL = "http://localhost:7770";
const INITIAL_AUTO_CONNECT_DELAY_MS = 900;
const INITIAL_CONNECT_RETRY_DELAY_MS = 1_200;
const OPENCLAW_CONTROL_UI_CLIENT_ID = "openclaw-control-ui";
const OPENCLAW_WEBCHAT_UI_CLIENT_ID = "webchat-ui";
const isAutoManagedAdapter = (adapterType: StudioGatewayAdapterType) =>
adapterType !== "custom";
export const resolveGatewayClientName = (
adapterType: StudioGatewayAdapterType,
gatewayUrl: string
) => {
if (adapterType !== "openclaw") {
return OPENCLAW_CONTROL_UI_CLIENT_ID;
}
return isLocalGatewayUrl(gatewayUrl)
? OPENCLAW_CONTROL_UI_CLIENT_ID
: OPENCLAW_WEBCHAT_UI_CLIENT_ID;
};
export const resolveInitialGatewayAutoConnectDelayMs = (
adapterType: StudioGatewayAdapterType
): number => {
@@ -526,6 +541,15 @@ const doctorFixHint =
const protocolMismatchHint =
"This gateway looks too old for Claw3D's protocol v3. Upgrade OpenClaw, use the Hermes adapter, or run `npm run demo-gateway` for a no-framework office demo.";
const tailscaleGatewayHint =
"If this is a remote OpenClaw/Tailscale gateway, confirm the Studio host can reach the `wss://...` address and approve the first device pairing on the gateway host with `openclaw devices approve --latest`.";
const pairingRequiredHint =
"This gateway is asking for first-time device approval. Run `openclaw devices approve --latest` on the gateway host, then restart Claw3D and reconnect from this browser.";
const requiresDeviceIdentityHint =
"This gateway rejected the client as a control UI without device identity. For remote OpenClaw/Tailscale connections, update to the latest Claw3D build and approve the device pairing on the gateway host.";
const isGatewayProtocolMismatchError = (error: GatewayResponseError) => {
if (error.code.trim().toUpperCase() !== "INVALID_REQUEST") return false;
const message = error.message.trim();
@@ -541,6 +565,18 @@ const formatGatewayError = (error: unknown) => {
if (error.code === "INVALID_REQUEST" && /invalid config/i.test(error.message)) {
return `Gateway error (${error.code}): ${error.message}. ${doctorFixHint}`;
}
if (error.code === "studio.upstream_timeout") {
return `Gateway error (${error.code}): ${error.message} ${tailscaleGatewayHint}`;
}
if (error.code === "studio.upstream_rejected") {
const lower = error.message.toLowerCase();
if (lower.includes("pairing required")) {
return `Gateway error (${error.code}): ${error.message}. ${pairingRequiredHint}`;
}
if (lower.includes("device identity")) {
return `Gateway error (${error.code}): ${error.message}. ${requiresDeviceIdentityHint}`;
}
}
return `Gateway error (${error.code}): ${error.message}`;
}
if (error instanceof Error) {
@@ -608,6 +644,8 @@ const NON_RETRYABLE_CONNECT_ERROR_CODES = new Set([
"studio.gateway_url_invalid",
"studio.settings_load_failed",
"studio.upstream_error",
"studio.upstream_timeout",
"studio.upstream_rejected",
]);
const isNonRetryableConnectErrorCode = (code: string | null): boolean => {
@@ -899,7 +937,7 @@ export const useGatewayConnection = (
gatewayUrl: resolveStudioProxyGatewayUrl(),
token,
authScopeKey: gatewayUrl,
clientName: "openclaw-control-ui",
clientName: resolveGatewayClientName(selectedAdapterType, gatewayUrl),
disableDeviceAuth: selectedAdapterType !== "openclaw",
});
lastError = null;