From 8e63dd44b6221e14b1e0d62ee01bf4e13f3310ea Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 5 Mar 2026 15:16:26 -0600 Subject: [PATCH] openclaw: accept webhook json ack in sse mode --- .../adapters/openclaw/src/server/execute.ts | 32 ++++++++++++++++++- server/src/__tests__/openclaw-adapter.test.ts | 28 ++++++++++++++-- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/packages/adapters/openclaw/src/server/execute.ts b/packages/adapters/openclaw/src/server/execute.ts index 5492bfbd..677cf231 100644 --- a/packages/adapters/openclaw/src/server/execute.ts +++ b/packages/adapters/openclaw/src/server/execute.ts @@ -83,6 +83,13 @@ function isTextRequiredResponse(responseText: string): boolean { return responseText.toLowerCase().includes("text required"); } +function isWebhookAcceptedResponse(parsed: Record | null): boolean { + if (!parsed) return false; + if (parsed.ok === true) return true; + const status = nonEmpty(parsed.status)?.toLowerCase(); + return status === "ok" || status === "accepted"; +} + async function sendJsonRequest(params: { url: string; method: string; @@ -576,6 +583,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise { expect((body.paperclip as Record).sessionKey).toBe("paperclip:issue:issue-123"); }); - it("fails when SSE endpoint does not return text/event-stream", async () => { + it("fails when SSE endpoint does not return text/event-stream and no compatibility fallback applies", async () => { const fetchMock = vi.fn().mockResolvedValue( - new Response(JSON.stringify({ ok: true }), { + new Response(JSON.stringify({ ok: false, error: "unexpected payload" }), { status: 200, statusText: "OK", headers: { @@ -140,6 +140,30 @@ describe("openclaw adapter execute", () => { expect(result.errorCode).toBe("openclaw_sse_expected_event_stream"); }); + it("treats webhook-style JSON ack as compatibility success when SSE endpoint returns JSON", async () => { + const fetchMock = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ ok: true, runId: "oc-run-1" }), { + status: 200, + statusText: "OK", + headers: { + "content-type": "application/json", + }, + }), + ); + vi.stubGlobal("fetch", fetchMock); + + const result = await execute( + buildContext({ + url: "https://agent.example/hooks/paperclip", + method: "POST", + }), + ); + + expect(result.exitCode).toBe(0); + expect(result.resultJson?.compatibilityMode).toBe("json_ack"); + expect(result.resultJson?.transportFallback).toBe("webhook"); + }); + 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" }),