mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-07 22:44:16 +00:00
fix(canvas): port remaining iOS branch stability fixes (#18228)
* fix(canvas): prevent snapshot disconnects on proxied gateways (cherry picked from commit 2a3c9f746a65f3301c0cfe58ebe6596fed06230f) * fix(canvas): accept url alias for present and navigate (cherry picked from commit 674ee86a0b776cbb738add1920a4031246125312) --------- Co-authored-by: Nimrod Gutman <nimrod.g@singular.net>
This commit is contained in:
@@ -85,7 +85,13 @@ public actor GatewayNodeSession {
|
||||
latch.resume(result)
|
||||
}
|
||||
timeoutTask = Task.detached {
|
||||
try? await Task.sleep(nanoseconds: UInt64(timeout) * 1_000_000)
|
||||
do {
|
||||
try await Task.sleep(nanoseconds: UInt64(timeout) * 1_000_000)
|
||||
} catch {
|
||||
// Expected when invoke finishes first and cancels the timeout task.
|
||||
return
|
||||
}
|
||||
guard !Task.isCancelled else { return }
|
||||
timeoutLogger.info("node invoke timeout fired id=\(request.id, privacy: .public)")
|
||||
latch.resume(BridgeInvokeResponse(
|
||||
id: request.id,
|
||||
|
||||
@@ -87,8 +87,13 @@ export function createCanvasTool(): AnyAgentTool {
|
||||
height: typeof params.height === "number" ? params.height : undefined,
|
||||
};
|
||||
const invokeParams: Record<string, unknown> = {};
|
||||
if (typeof params.target === "string" && params.target.trim()) {
|
||||
invokeParams.url = params.target.trim();
|
||||
// Accept both `target` and `url` for present to match common caller expectations.
|
||||
// `target` remains the canonical field for CLI compatibility.
|
||||
const presentTarget =
|
||||
readStringParam(params, "target", { trim: true }) ??
|
||||
readStringParam(params, "url", { trim: true });
|
||||
if (presentTarget) {
|
||||
invokeParams.url = presentTarget;
|
||||
}
|
||||
if (
|
||||
Number.isFinite(placement.x) ||
|
||||
@@ -105,7 +110,10 @@ export function createCanvasTool(): AnyAgentTool {
|
||||
await invoke("canvas.hide", undefined);
|
||||
return jsonResult({ ok: true });
|
||||
case "navigate": {
|
||||
const url = readStringParam(params, "url", { required: true });
|
||||
// Support `target` as an alias so callers can reuse the same field across present/navigate.
|
||||
const url =
|
||||
readStringParam(params, "url", { trim: true }) ??
|
||||
readStringParam(params, "target", { required: true, trim: true, label: "url" });
|
||||
await invoke("canvas.navigate", { url });
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export const MAX_PAYLOAD_BYTES = 8 * 1024 * 1024; // cap incoming frame size (~8 MiB; fits ~5,000,000 decoded bytes as base64 + JSON overhead)
|
||||
export const MAX_BUFFERED_BYTES = 16 * 1024 * 1024; // per-connection send buffer limit (2x max payload)
|
||||
// Keep server maxPayload aligned with gateway client maxPayload so high-res canvas snapshots
|
||||
// don't get disconnected mid-invoke with "Max payload size exceeded".
|
||||
export const MAX_PAYLOAD_BYTES = 25 * 1024 * 1024;
|
||||
export const MAX_BUFFERED_BYTES = 50 * 1024 * 1024; // per-connection send buffer limit (2x max payload)
|
||||
|
||||
const DEFAULT_MAX_CHAT_HISTORY_MESSAGES_BYTES = 6 * 1024 * 1024; // keep history responses comfortably under client WS limits
|
||||
let maxChatHistoryMessagesBytes = DEFAULT_MAX_CHAT_HISTORY_MESSAGES_BYTES;
|
||||
|
||||
@@ -25,14 +25,25 @@ const normalizeHost = (value: HostSource, rejectLoopback: boolean) => {
|
||||
return trimmed;
|
||||
};
|
||||
|
||||
const parseHostHeader = (value: HostSource) => {
|
||||
type ParsedHostHeader = {
|
||||
host: string;
|
||||
port?: number;
|
||||
};
|
||||
|
||||
const parseHostHeader = (value: HostSource): ParsedHostHeader => {
|
||||
if (!value) {
|
||||
return "";
|
||||
return { host: "" };
|
||||
}
|
||||
try {
|
||||
return new URL(`http://${String(value).trim()}`).hostname;
|
||||
const parsed = new URL(`http://${String(value).trim()}`);
|
||||
const portRaw = parsed.port.trim();
|
||||
const port = portRaw ? Number.parseInt(portRaw, 10) : undefined;
|
||||
return {
|
||||
host: parsed.hostname,
|
||||
port: Number.isFinite(port) ? port : undefined,
|
||||
};
|
||||
} catch {
|
||||
return "";
|
||||
return { host: "" };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -54,13 +65,29 @@ export function resolveCanvasHostUrl(params: CanvasHostUrlParams) {
|
||||
(parseForwardedProto(params.forwardedProto)?.trim() === "https" ? "https" : "http");
|
||||
|
||||
const override = normalizeHost(params.hostOverride, true);
|
||||
const requestHost = normalizeHost(parseHostHeader(params.requestHost), !!override);
|
||||
const parsedRequestHost = parseHostHeader(params.requestHost);
|
||||
const requestHost = normalizeHost(parsedRequestHost.host, !!override);
|
||||
const localAddress = normalizeHost(params.localAddress, Boolean(override || requestHost));
|
||||
|
||||
const host = override || requestHost || localAddress;
|
||||
if (!host) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// When the websocket is proxied over HTTPS (for example Tailscale Serve), the gateway's
|
||||
// internal listener still runs on 18789. In that case, expose the public port instead of
|
||||
// advertising the internal one back to clients.
|
||||
let exposedPort = port;
|
||||
if (!override && requestHost && port === 18789) {
|
||||
if (parsedRequestHost.port && parsedRequestHost.port > 0) {
|
||||
exposedPort = parsedRequestHost.port;
|
||||
} else if (scheme === "https") {
|
||||
exposedPort = 443;
|
||||
} else if (scheme === "http") {
|
||||
exposedPort = 80;
|
||||
}
|
||||
}
|
||||
|
||||
const formatted = host.includes(":") ? `[${host}]` : host;
|
||||
return `${scheme}://${formatted}:${port}`;
|
||||
return `${scheme}://${formatted}:${exposedPort}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user