From 4b90784183336d00191cf87df236a0847b012a0b Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 5 Mar 2026 14:56:49 -0600 Subject: [PATCH] fix(openclaw): fallback to wake compatibility for /hooks/wake in sse mode --- .../adapters/openclaw/src/server/execute.ts | 112 +++++++++++++++++- server/src/__tests__/openclaw-adapter.test.ts | 22 ++++ 2 files changed, 133 insertions(+), 1 deletion(-) diff --git a/packages/adapters/openclaw/src/server/execute.ts b/packages/adapters/openclaw/src/server/execute.ts index bf903dfb..a1e17a34 100644 --- a/packages/adapters/openclaw/src/server/execute.ts +++ b/packages/adapters/openclaw/src/server/execute.ts @@ -456,7 +456,56 @@ export async function execute(ctx: AdapterExecutionContext): Promise controller.abort(), timeoutSec * 1000); try { + const preferWakeTextPayload = shouldUseWakeTextPayload(url); + if (transport === "sse") { + if (preferWakeTextPayload) { + await onLog( + "stdout", + "[openclaw] /hooks/wake compatibility endpoint does not stream SSE; falling back to wake text payload\n", + ); + const retry = await sendWebhookRequest({ + url, + method, + headers, + payload: wakeTextBody, + onLog, + signal: controller.signal, + }); + + if (retry.response.ok) { + return { + exitCode: 0, + signal: null, + timedOut: false, + provider: "openclaw", + model: null, + summary: `OpenClaw webhook ${method} ${url} (wake compatibility fallback)`, + resultJson: { + status: retry.response.status, + statusText: retry.response.statusText, + compatibilityMode: "wake_text", + transportFallback: "webhook", + response: parseOpenClawResponse(retry.responseText) ?? retry.responseText, + }, + }; + } + return { + exitCode: 1, + signal: null, + timedOut: false, + errorMessage: `OpenClaw webhook failed with status ${retry.response.status}`, + errorCode: "openclaw_http_error", + resultJson: { + status: retry.response.status, + statusText: retry.response.statusText, + compatibilityMode: "wake_text", + transportFallback: "webhook", + response: parseOpenClawResponse(retry.responseText) ?? retry.responseText, + }, + }; + } + const sseHeaders = { ...headers, accept: "text/event-stream", @@ -472,6 +521,37 @@ export async function execute(ctx: AdapterExecutionContext): Promise { expect(result.errorCode).toBe("openclaw_sse_expected_event_stream"); }); + it("falls back to wake text payload when SSE is configured against /hooks/wake", async () => { + const fetchMock = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ ok: true }), { status: 200, statusText: "OK" }), + ); + vi.stubGlobal("fetch", fetchMock); + + const result = await execute( + buildContext({ + url: "https://agent.example/hooks/wake", + method: "POST", + }), + ); + + expect(result.exitCode).toBe(0); + expect(fetchMock).toHaveBeenCalledTimes(1); + const body = JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body ?? "{}")) as Record; + expect(body.mode).toBe("now"); + expect(typeof body.text).toBe("string"); + expect(result.resultJson?.compatibilityMode).toBe("wake_text"); + expect(result.resultJson?.transportFallback).toBe("webhook"); + }); + it("uses wake text payload for /hooks/wake endpoints in webhook mode", async () => { const fetchMock = vi.fn().mockResolvedValue( new Response(JSON.stringify({ ok: true }), { status: 200, statusText: "OK" }),