From cef78cf045f8a089a1bab8e50255d2833ae83fe9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 06:13:57 +0000 Subject: [PATCH 1/6] build(deps): bump actions/checkout from 7.0.0 to 7.0.1 Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0...3d3c42e5aac5ba805825da76410c181273ba90b1) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 14 +++++++------- .github/workflows/nuget.yml | 2 +- .github/workflows/release.yml | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a8a8712..9320111e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: go-whatsapp-security: runs-on: ubuntu-latest steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Setup Go uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 @@ -49,7 +49,7 @@ jobs: build-and-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Setup .NET uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 @@ -102,7 +102,7 @@ jobs: runs-on: ubuntu-latest if: github.event_name != 'schedule' steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Setup .NET uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 @@ -152,7 +152,7 @@ jobs: if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' timeout-minutes: 60 steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Setup .NET uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 @@ -225,7 +225,7 @@ jobs: needs: build-and-test runs-on: ubuntu-latest steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Setup .NET uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 @@ -268,7 +268,7 @@ jobs: permissions: packages: write steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c @@ -299,7 +299,7 @@ jobs: env: OPENCLAW_PUBLIC_SMOKE: "1" steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Setup .NET uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index 29075487..431c17ed 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -21,7 +21,7 @@ jobs: env: PACKAGE_VERSION: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Setup .NET uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eb821fe2..20a29954 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,7 +53,7 @@ jobs: rid: osx-arm64 steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Setup .NET uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 @@ -211,7 +211,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: Download release artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c From 3882ba0da37c533489cb95dfb0360815699d530a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 06:14:02 +0000 Subject: [PATCH 2/6] build(deps): bump actions/setup-dotnet from 5.4.0 to 6.0.0 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5.4.0 to 6.0.0. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/26b0ec14cb23fa6904739307f278c14f94c95bf1...a98b56852c35b8e3190ac28c8c2271da59106c68) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 10 +++++----- .github/workflows/nuget.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a8a8712..39cfeb71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - name: Setup .NET - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 with: dotnet-version: ${{ env.DOTNET_VERSION }} @@ -105,7 +105,7 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - name: Setup .NET - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 with: dotnet-version: ${{ env.DOTNET_VERSION }} @@ -155,7 +155,7 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - name: Setup .NET - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 with: dotnet-version: ${{ env.DOTNET_VERSION }} @@ -228,7 +228,7 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - name: Setup .NET - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 with: dotnet-version: ${{ env.DOTNET_VERSION }} @@ -302,7 +302,7 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - name: Setup .NET - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 with: dotnet-version: ${{ env.DOTNET_VERSION }} diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index 29075487..9358d10f 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - name: Setup .NET - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 with: dotnet-version: ${{ env.DOTNET_VERSION }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eb821fe2..daf9ec30 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -56,7 +56,7 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - name: Setup .NET - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 with: dotnet-version: ${{ env.DOTNET_VERSION }} From cbc1472dc44baea44b708aee4672222f09b6d863 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 06:14:08 +0000 Subject: [PATCH 3/6] build(deps): bump actions/setup-go from 6.5.0 to 7.0.0 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6.5.0 to 7.0.0. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/924ae3a1cded613372ab5595356fb5720e22ba16...b7ad1dad31e06c5925ef5d2fc7ad053ef454303e) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a8a8712..46dc13e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - name: Setup Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e with: go-version: "1.26.5" cache-dependency-path: src/whatsapp-whatsmeow-worker/go.sum From e7adfcb461ca41d29ec3be784ed2f36ede3892d1 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Wed, 22 Jul 2026 08:10:59 +0800 Subject: [PATCH 4/6] Handle WebSocket heartbeats end-to-end Adds a periodic heartbeat sender in `webchat.js` (configurable via `heartbeatIntervalMs`) and ensures heartbeat timers are started/stopped with socket lifecycle events. On the server side, `WebSocketChannel` now treats `heartbeat` envelopes as ignorable so they do not emit inbound user messages. Includes a new test to verify heartbeat envelopes are ignored. --- src/OpenClaw.Channels/WebSocketChannel.cs | 14 ++++++++-- src/OpenClaw.Gateway/wwwroot/webchat.js | 30 +++++++++++++++++++++ src/OpenClaw.Tests/WebSocketChannelTests.cs | 21 +++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/OpenClaw.Channels/WebSocketChannel.cs b/src/OpenClaw.Channels/WebSocketChannel.cs index 29f48176..1bcc0d35 100644 --- a/src/OpenClaw.Channels/WebSocketChannel.cs +++ b/src/OpenClaw.Channels/WebSocketChannel.cs @@ -123,6 +123,9 @@ await SendEnvelopeToStateAsync( continue; } + if (IsIgnorableClientEnvelope(parsed.Type)) + continue; + var msg = new InboundMessage { ChannelId = ChannelId, @@ -142,8 +145,7 @@ await SendEnvelopeToStateAsync( ValueJson = parsed.CanvasEnvelope?.ValueJson, Sequence = parsed.CanvasEnvelope?.Sequence, ApprovalId = parsed.ApprovalId, - Approved = parsed.Approved, - RequestCancellation = ct + Approved = parsed.Approved }; if (OnMessageReceived is not null) @@ -558,6 +560,11 @@ private static ParsedWsInbound TryParseClientEnvelope(string payload) return new ParsedWsInbound(true, env.Type, "", env.SessionId, env.MessageId, env.ReplyToMessageId, env.ApprovalId, env.Approved); } + if (env is not null && IsIgnorableClientEnvelope(env.Type)) + { + return new ParsedWsInbound(true, env.Type, "", env.SessionId, env.MessageId, env.ReplyToMessageId, null, null); + } + if (env is not null && IsCanvasClientEnvelope(env.Type)) { var text = env.Type switch @@ -586,6 +593,9 @@ private static ParsedWsInbound TryParseClientEnvelope(string payload) return new ParsedWsInbound(false, null, payload, null, null, null, null, null, null); } + private static bool IsIgnorableClientEnvelope(string? type) + => type is "heartbeat"; + private async ValueTask HandleCanvasClientEnvelopeAsync(string clientId, WsClientEnvelope envelope, CancellationToken ct) { if (OnCanvasClientEnvelopeReceived is not null) diff --git a/src/OpenClaw.Gateway/wwwroot/webchat.js b/src/OpenClaw.Gateway/wwwroot/webchat.js index a941961a..878c3f00 100644 --- a/src/OpenClaw.Gateway/wwwroot/webchat.js +++ b/src/OpenClaw.Gateway/wwwroot/webchat.js @@ -145,6 +145,7 @@ let activeRawContent = ""; let isAwaitingResponse = false; let reconnectAttempts = 0; let reconnectTimer = null; +let heartbeatTimer = null; let streamRenderTimer = null; let liveResponseHasAudio = false; let liveAudioQueue = []; @@ -172,6 +173,7 @@ let pendingFileUrls = []; // non-image files queued for upload const WEBCHAT_CONFIG = { streamRenderDebounceMs: Math.max(20, Number(window.OPENCLAW_WEBCHAT_CONFIG?.streamRenderDebounceMs ?? 120)), + heartbeatIntervalMs: Math.max(1000, Number(window.OPENCLAW_WEBCHAT_CONFIG?.heartbeatIntervalMs ?? 45000)), initialReconnectDelayMs: Math.max(250, Number(window.OPENCLAW_WEBCHAT_CONFIG?.initialReconnectDelayMs ?? 1000)), maxReconnectDelayMs: Math.max(1000, Number(window.OPENCLAW_WEBCHAT_CONFIG?.maxReconnectDelayMs ?? 30000)), reconnectBackoffFactor: Math.max(1.1, Number(window.OPENCLAW_WEBCHAT_CONFIG?.reconnectBackoffFactor ?? 2)), @@ -946,6 +948,31 @@ function resetActiveResponse() { activeRawContent = ''; } +function clearHeartbeatTimer() { + if (!heartbeatTimer) { + return; + } + + clearInterval(heartbeatTimer); + heartbeatTimer = null; +} + +function startHeartbeatLoop() { + clearHeartbeatTimer(); + + heartbeatTimer = setInterval(() => { + if (!ws || ws.readyState !== WebSocket.OPEN) { + return; + } + + try { + ws.send(JSON.stringify({ type: 'heartbeat' })); + } catch (_) { + clearHeartbeatTimer(); + } + }, WEBCHAT_CONFIG.heartbeatIntervalMs); +} + function createRow(type) { const row = document.createElement('div'); row.className = `message-row ${type}-row`; @@ -2406,6 +2433,7 @@ async function openDoctorDiagnostics(addToChat = false) { } function connect() { + clearHeartbeatTimer(); setConnectionState(reconnectAttempts > 0 ? 'reconnecting' : 'connecting'); appendSystem('Connecting to OpenClaw.NET Gateway...'); const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; @@ -2427,6 +2455,7 @@ function connect() { reconnectTimer = null; } dismissFirstRunCard(); + startHeartbeatLoop(); refreshChatState(); appendSystem('Connected successfully.'); sendCanvasReady(); @@ -2566,6 +2595,7 @@ function connect() { }; ws.onclose = (event) => { + clearHeartbeatTimer(); updateComposerAvailability(); resetActiveResponse(); diff --git a/src/OpenClaw.Tests/WebSocketChannelTests.cs b/src/OpenClaw.Tests/WebSocketChannelTests.cs index 2a841c19..c603fd77 100644 --- a/src/OpenClaw.Tests/WebSocketChannelTests.cs +++ b/src/OpenClaw.Tests/WebSocketChannelTests.cs @@ -238,6 +238,27 @@ public async Task HandleConnectionAsync_RoutesCanvasReadyWithoutUserMessage() Assert.False(messageObserved); } + [Fact] + public async Task HandleConnectionAsync_IgnoresHeartbeatEnvelopeWithoutUserMessage() + { + var channel = new WebSocketChannel(new WebSocketConfig { MaxMessageBytes = 1024 }); + var ws = new TestWebSocket(); + + ws.QueueReceiveText("""{"type":"heartbeat"}"""); + ws.QueueClose(); + + var messageObserved = false; + channel.OnMessageReceived += (_, _) => + { + messageObserved = true; + return ValueTask.CompletedTask; + }; + + await channel.HandleConnectionAsync(ws, "client", IPAddress.Loopback, TestContext.Current.CancellationToken); + + Assert.False(messageObserved); + } + [Fact] public async Task HandleConnectionAsync_RoutesCanvasAckAndResultsWithoutUserMessage() { From c3c53459d5d247ad98aaa4b688fd7e038d0fad2c Mon Sep 17 00:00:00 2001 From: geffzhang Date: Wed, 22 Jul 2026 08:29:32 +0800 Subject: [PATCH 5/6] Forward WebSocket request cancellation Pass the connection request cancellation token through `InboundMessage` in `WebSocketChannel` so downstream handlers can observe client/request aborts. Adds a regression test to ensure the token is forwarded and remains cancellable. --- src/OpenClaw.Channels/WebSocketChannel.cs | 1 + src/OpenClaw.Tests/WebSocketChannelTests.cs | 29 +++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/OpenClaw.Channels/WebSocketChannel.cs b/src/OpenClaw.Channels/WebSocketChannel.cs index 1bcc0d35..75e0d596 100644 --- a/src/OpenClaw.Channels/WebSocketChannel.cs +++ b/src/OpenClaw.Channels/WebSocketChannel.cs @@ -130,6 +130,7 @@ await SendEnvelopeToStateAsync( { ChannelId = ChannelId, SenderId = clientId, + RequestCancellation = ct, AuthenticatedUserId = authenticatedUserId, SessionId = parsed.SessionId, Type = parsed.Type, diff --git a/src/OpenClaw.Tests/WebSocketChannelTests.cs b/src/OpenClaw.Tests/WebSocketChannelTests.cs index c603fd77..eeda344f 100644 --- a/src/OpenClaw.Tests/WebSocketChannelTests.cs +++ b/src/OpenClaw.Tests/WebSocketChannelTests.cs @@ -208,6 +208,35 @@ await channel.HandleConnectionAsync( Assert.Equal("acct-123", received!.AuthenticatedUserId); } + [Fact] + public async Task HandleConnectionAsync_ForwardsRequestCancellationToken() + { + var channel = new WebSocketChannel(new WebSocketConfig { MaxMessageBytes = 1024 }); + var ws = new TestWebSocket(); + + ws.QueueReceiveText("hello"); + ws.QueueClose(); + + InboundMessage? received = null; + channel.OnMessageReceived += (msg, _) => + { + received = msg; + return ValueTask.CompletedTask; + }; + + using var requestCts = new CancellationTokenSource(); + await channel.HandleConnectionAsync( + ws, + "client", + IPAddress.Loopback, + requestCts.Token, + authenticatedUserId: null); + + Assert.NotNull(received); + Assert.Equal(requestCts.Token, received!.RequestCancellation); + Assert.True(received.RequestCancellation.CanBeCanceled); + } + [Fact] public async Task HandleConnectionAsync_RoutesCanvasReadyWithoutUserMessage() { From 3cb509f303711ca68dcfdb44fad576205f4e0849 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Wed, 22 Jul 2026 13:11:14 +0800 Subject: [PATCH 6/6] Harden webchat heartbeat interval parsing Sanitize `heartbeatIntervalMs` by handling non-finite config values and clamping the result to a safe timer range (1000 to 2147483647 ms). This avoids invalid or overflow-prone intervals when reading `OPENCLAW_WEBCHAT_CONFIG`. --- src/OpenClaw.Gateway/wwwroot/webchat.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/OpenClaw.Gateway/wwwroot/webchat.js b/src/OpenClaw.Gateway/wwwroot/webchat.js index 878c3f00..c1894e08 100644 --- a/src/OpenClaw.Gateway/wwwroot/webchat.js +++ b/src/OpenClaw.Gateway/wwwroot/webchat.js @@ -173,7 +173,11 @@ let pendingFileUrls = []; // non-image files queued for upload const WEBCHAT_CONFIG = { streamRenderDebounceMs: Math.max(20, Number(window.OPENCLAW_WEBCHAT_CONFIG?.streamRenderDebounceMs ?? 120)), - heartbeatIntervalMs: Math.max(1000, Number(window.OPENCLAW_WEBCHAT_CONFIG?.heartbeatIntervalMs ?? 45000)), + heartbeatIntervalMs: (() => { + const raw = Number(window.OPENCLAW_WEBCHAT_CONFIG?.heartbeatIntervalMs ?? 45000); + const safe = Number.isFinite(raw) ? raw : 45000; + return Math.min(2147483647, Math.max(1000, safe)); + })(), initialReconnectDelayMs: Math.max(250, Number(window.OPENCLAW_WEBCHAT_CONFIG?.initialReconnectDelayMs ?? 1000)), maxReconnectDelayMs: Math.max(1000, Number(window.OPENCLAW_WEBCHAT_CONFIG?.maxReconnectDelayMs ?? 30000)), reconnectBackoffFactor: Math.max(1.1, Number(window.OPENCLAW_WEBCHAT_CONFIG?.reconnectBackoffFactor ?? 2)),