diff --git a/packages/adapters/openclaw/src/server/execute-common.ts b/packages/adapters/openclaw/src/server/execute-common.ts index ab2a0693..fd0d6484 100644 --- a/packages/adapters/openclaw/src/server/execute-common.ts +++ b/packages/adapters/openclaw/src/server/execute-common.ts @@ -93,28 +93,6 @@ export function isOpenResponsesEndpoint(url: string): boolean { } } -export function normalizeWebhookInvocationUrl(url: string): { - url: string; - normalizedFromOpenResponses: boolean; -} { - try { - const parsed = new URL(url); - const path = parsed.pathname; - const normalizedPath = path.toLowerCase(); - const suffix = "/v1/responses"; - - if (normalizedPath !== suffix && !normalizedPath.endsWith(suffix)) { - return { url: parsed.toString(), normalizedFromOpenResponses: false }; - } - - const prefix = path.slice(0, path.length - suffix.length); - parsed.pathname = `${prefix}/hooks/wake`; - return { url: parsed.toString(), normalizedFromOpenResponses: true }; - } catch { - return { url, normalizedFromOpenResponses: false }; - } -} - export function toStringRecord(value: unknown): Record { const parsed = parseObject(value); const out: Record = {}; diff --git a/packages/adapters/openclaw/src/server/execute-webhook.ts b/packages/adapters/openclaw/src/server/execute-webhook.ts index 8d687b11..f76d6729 100644 --- a/packages/adapters/openclaw/src/server/execute-webhook.ts +++ b/packages/adapters/openclaw/src/server/execute-webhook.ts @@ -8,7 +8,6 @@ import { isTextRequiredResponse, isWakeCompatibilityRetryableResponse, isWakeCompatibilityEndpoint, - normalizeWebhookInvocationUrl, readAndLogResponseText, redactForLog, sendJsonRequest, @@ -93,35 +92,29 @@ async function sendWebhookRequest(params: { export async function executeWebhook(ctx: AdapterExecutionContext, url: string): Promise { const { onLog, onMeta, context } = ctx; const state = buildExecutionState(ctx); - const webhookTarget = normalizeWebhookInvocationUrl(url); - const webhookUrl = webhookTarget.url; if (onMeta) { await onMeta({ adapterType: "openclaw", command: "webhook", - commandArgs: [state.method, webhookUrl], + commandArgs: [state.method, url], context, }); } const headers = { ...state.headers }; - if ( - isOpenResponsesEndpoint(webhookUrl) && - !headers["x-openclaw-session-key"] && - !headers["X-OpenClaw-Session-Key"] - ) { + if (isOpenResponsesEndpoint(url) && !headers["x-openclaw-session-key"] && !headers["X-OpenClaw-Session-Key"]) { headers["x-openclaw-session-key"] = state.sessionKey; } const webhookBody = buildWebhookBody({ - url: webhookUrl, + url, state, context, configModel: ctx.config.model, }); const wakeCompatibilityBody = buildWakeCompatibilityPayload(state.wakeText); - const preferWakeCompatibilityBody = isWakeCompatibilityEndpoint(webhookUrl); + const preferWakeCompatibilityBody = isWakeCompatibilityEndpoint(url); const initialBody = preferWakeCompatibilityBody ? wakeCompatibilityBody : webhookBody; const outboundHeaderKeys = Object.keys(headers).sort(); @@ -134,13 +127,7 @@ export async function executeWebhook(ctx: AdapterExecutionContext, url: string): `[openclaw] outbound payload (redacted): ${stringifyForLog(redactForLog(initialBody), 12_000)}\n`, ); await onLog("stdout", `[openclaw] outbound header keys: ${outboundHeaderKeys.join(", ")}\n`); - if (webhookTarget.normalizedFromOpenResponses) { - await onLog( - "stdout", - `[openclaw] webhook transport normalized /v1/responses endpoint to ${webhookUrl}\n`, - ); - } - await onLog("stdout", `[openclaw] invoking ${state.method} ${webhookUrl} (transport=webhook)\n`); + await onLog("stdout", `[openclaw] invoking ${state.method} ${url} (transport=webhook)\n`); if (preferWakeCompatibilityBody) { await onLog("stdout", "[openclaw] using wake text payload for /hooks/wake compatibility\n"); @@ -151,7 +138,7 @@ export async function executeWebhook(ctx: AdapterExecutionContext, url: string): try { const initialResponse = await sendWebhookRequest({ - url: webhookUrl, + url, method: state.method, headers, payload: initialBody, @@ -170,7 +157,7 @@ export async function executeWebhook(ctx: AdapterExecutionContext, url: string): ); const retryResponse = await sendWebhookRequest({ - url: webhookUrl, + url, method: state.method, headers, payload: wakeCompatibilityBody, @@ -185,7 +172,7 @@ export async function executeWebhook(ctx: AdapterExecutionContext, url: string): timedOut: false, provider: "openclaw", model: null, - summary: `OpenClaw webhook ${state.method} ${webhookUrl} (wake compatibility)`, + summary: `OpenClaw webhook ${state.method} ${url} (wake compatibility)`, resultJson: { status: retryResponse.response.status, statusText: retryResponse.response.statusText, @@ -240,7 +227,7 @@ export async function executeWebhook(ctx: AdapterExecutionContext, url: string): timedOut: false, provider: "openclaw", model: null, - summary: `OpenClaw webhook ${state.method} ${webhookUrl}`, + summary: `OpenClaw webhook ${state.method} ${url}`, resultJson: { status: initialResponse.response.status, statusText: initialResponse.response.statusText, diff --git a/packages/adapters/openclaw/src/server/test.ts b/packages/adapters/openclaw/src/server/test.ts index 6a5f4daa..00e252ad 100644 --- a/packages/adapters/openclaw/src/server/test.ts +++ b/packages/adapters/openclaw/src/server/test.ts @@ -34,11 +34,6 @@ function isWakePath(pathname: string): boolean { return value === "/hooks/wake" || value.endsWith("/hooks/wake"); } -function isOpenResponsesPath(pathname: string): boolean { - const value = pathname.trim().toLowerCase(); - return value === "/v1/responses" || value.endsWith("/v1/responses"); -} - function normalizeTransport(value: unknown): "sse" | "webhook" | null { const normalized = asString(value, "sse").trim().toLowerCase(); if (!normalized || normalized === "sse") return "sse"; @@ -176,16 +171,6 @@ export async function testEnvironment( hint: "Use an endpoint that returns text/event-stream for the full run duration.", }); } - - if (streamTransport === "webhook" && isOpenResponsesPath(url.pathname)) { - checks.push({ - code: "openclaw_webhook_endpoint_normalized", - level: "warn", - message: - "Webhook transport is configured with a /v1/responses endpoint. Runtime will normalize this to /hooks/wake.", - hint: "Set endpoint path to /hooks/wake to avoid ambiguous transport behavior.", - }); - } } if (!streamTransport) { diff --git a/server/src/__tests__/openclaw-adapter.test.ts b/server/src/__tests__/openclaw-adapter.test.ts index 6f5b0667..e13319ba 100644 --- a/server/src/__tests__/openclaw-adapter.test.ts +++ b/server/src/__tests__/openclaw-adapter.test.ts @@ -564,7 +564,7 @@ describe("openclaw adapter execute", () => { expect((body.paperclip as Record).streamTransport).toBe("webhook"); }); - it("normalizes /v1/responses to /hooks/wake for webhook transport", async () => { + it("uses OpenResponses payload shape for webhook transport against /v1/responses", async () => { const fetchMock = vi.fn().mockResolvedValue( new Response(JSON.stringify({ ok: true }), { status: 200, @@ -586,12 +586,16 @@ describe("openclaw adapter execute", () => { expect(result.exitCode).toBe(0); expect(fetchMock).toHaveBeenCalledTimes(1); - expect(fetchMock.mock.calls[0]?.[0]).toBe("https://agent.example/hooks/wake"); const body = JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body ?? "{}")) as Record; - expect(body.mode).toBe("now"); - expect(String(body.text ?? "")).toContain("PAPERCLIP_RUN_ID=run-123"); - expect(body.model).toBeUndefined(); - expect(body.input).toBeUndefined(); + expect(body.foo).toBe("bar"); + expect(body.stream).toBe(false); + expect(body.model).toBe("openclaw"); + expect(String(body.input ?? "")).toContain("PAPERCLIP_RUN_ID=run-123"); + const metadata = body.metadata as Record; + expect(metadata.PAPERCLIP_RUN_ID).toBe("run-123"); + expect(metadata.paperclip_session_key).toBe("paperclip"); + expect(metadata.paperclip_stream_transport).toBe("webhook"); + expect(body.paperclip).toBeUndefined(); }); it("uses wake compatibility payloads for /hooks/wake when transport=webhook", async () => { @@ -645,7 +649,7 @@ describe("openclaw adapter execute", () => { const result = await execute( buildContext({ - url: "https://agent.example/webhook", + url: "https://agent.example/v1/responses", streamTransport: "webhook", }), ); @@ -654,13 +658,13 @@ describe("openclaw adapter execute", () => { expect(fetchMock).toHaveBeenCalledTimes(2); const firstBody = JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body ?? "{}")) as Record; const secondBody = JSON.parse(String(fetchMock.mock.calls[1]?.[1]?.body ?? "{}")) as Record; - expect(firstBody.paperclip).toBeTruthy(); - expect(String(firstBody.text ?? "")).toContain("PAPERCLIP_RUN_ID=run-123"); + expect(firstBody.model).toBe("openclaw"); + expect(String(firstBody.input ?? "")).toContain("PAPERCLIP_RUN_ID=run-123"); expect(secondBody.mode).toBe("now"); expect(String(secondBody.text ?? "")).toContain("PAPERCLIP_RUN_ID=run-123"); }); - it("retries webhook payloads when endpoint reports missing string input", async () => { + it("retries webhook payloads when /v1/responses reports missing string input", async () => { const fetchMock = vi .fn() .mockResolvedValueOnce( @@ -693,7 +697,7 @@ describe("openclaw adapter execute", () => { const result = await execute( buildContext({ - url: "https://agent.example/webhook", + url: "https://agent.example/v1/responses", streamTransport: "webhook", }), ); @@ -803,27 +807,6 @@ describe("openclaw adapter environment checks", () => { expect(configured?.level).toBe("info"); expect(wakeIncompatible).toBeUndefined(); }); - - it("warns when webhook transport is configured with a /v1/responses endpoint", async () => { - const fetchMock = vi - .fn() - .mockResolvedValue(new Response(null, { status: 405, statusText: "Method Not Allowed" })); - vi.stubGlobal("fetch", fetchMock); - - const result = await testEnvironment({ - companyId: "company-123", - adapterType: "openclaw", - config: { - url: "https://agent.example/v1/responses", - streamTransport: "webhook", - }, - }); - - const normalizedWarning = result.checks.find( - (entry) => entry.code === "openclaw_webhook_endpoint_normalized", - ); - expect(normalizedWarning?.level).toBe("warn"); - }); }); describe("onHireApproved", () => {